summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorick Peterse <yorickpeterse@gmail.com>2017-06-13 16:44:55 +0200
committerYorick Peterse <yorickpeterse@gmail.com>2017-06-13 17:07:39 +0200
commit7d62017c0ed17b64e42f9e23bca76bf6f1f172a0 (patch)
tree2bcebdd5a5d80172079554447dcd660398f147d5
parent323d9f1f5767d74d4aaa858b9e07677430571ac3 (diff)
downloadgitlab-ce-refactor-projects-finder-init-collection.tar.gz
Refactor ProjectsFinder#init_collectionrefactor-projects-finder-init-collection
This changes ProjectsFinder#init_collection so it no longer relies on a UNION. For example, to get starred projects of a user we used to run: SELECT projects.* FROM projects WHERE projects.pending_delete = 'f' AND ( projects.id IN ( SELECT projects.id FROM projects INNER JOIN users_star_projects ON users_star_projects.project_id = projects.id INNER JOIN project_authorizations ON projects.id = project_authorizations.project_id WHERE projects.pending_delete = 'f' AND project_authorizations.user_id = 1 AND users_star_projects.user_id = 1 UNION SELECT projects.id FROM projects INNER JOIN users_star_projects ON users_star_projects.project_id = projects.id WHERE projects.visibility_level IN (20, 10) AND users_star_projects.user_id = 1 ) ) ORDER BY projects.id DESC; With these changes the above query is turned into the following instead: SELECT projects.* FROM projects INNER JOIN users_star_projects ON users_star_projects.project_id = projects.id WHERE projects.pending_delete = 'f' AND ( EXISTS ( SELECT 1 FROM project_authorizations WHERE project_authorizations.user_id = 1 AND (project_id = projects.id) ) OR projects.visibility_level IN (20,10) ) AND users_star_projects.user_id = 1 ORDER BY projects.id DESC; This query in turn produces a better execution plan and takes less time, though the difference is only a few milliseconds (this however depends on the amount of data involved and additional conditions that may be added).
-rw-r--r--app/finders/projects_finder.rb76
-rw-r--r--changelogs/unreleased/refactor-projects-finder-init-collection.yml5
-rw-r--r--lib/gitlab/visibility_level.rb28
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb31
4 files changed, 109 insertions, 31 deletions
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 5bf722d1ec6..72e9c7a1cd7 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -28,34 +28,72 @@ class ProjectsFinder < UnionFinder
end
def execute
- items = init_collection
- items = items.map do |item|
- item = by_ids(item)
- item = by_personal(item)
- item = by_starred(item)
- item = by_trending(item)
- item = by_visibilty_level(item)
- item = by_tags(item)
- item = by_search(item)
- by_archived(item)
- end
- items = union(items)
- sort(items)
+ collection = init_collection
+ collection = by_ids(collection)
+ collection = by_personal(collection)
+ collection = by_starred(collection)
+ collection = by_trending(collection)
+ collection = by_visibilty_level(collection)
+ collection = by_tags(collection)
+ collection = by_search(collection)
+ collection = by_archived(collection)
+
+ sort(collection)
end
private
def init_collection
- projects = []
+ if current_user
+ collection_with_user
+ else
+ collection_without_user
+ end
+ end
- if params[:owned].present?
- projects << current_user.owned_projects if current_user
+ def collection_with_user
+ if owned_projects?
+ current_user.owned_projects
else
- projects << current_user.authorized_projects if current_user
- projects << Project.unscoped.public_to_user(current_user) unless params[:non_public].present?
+ if private_only?
+ current_user.authorized_projects
+ else
+ collection_with_user_and_public_projects
+ end
end
+ end
+
+ # Builds a collection for a signed in user that includes additional projects
+ # such as public and internal ones.
+ #
+ # This method manually constructs some WHERE conditions in order to ensure the
+ # produced query is as efficient as possible.
+ def collection_with_user_and_public_projects
+ levels = Gitlab::VisibilityLevel.levels_for_user(current_user)
+ authorized = current_user.project_authorizations.
+ select(1).
+ where('project_id = projects.id')
+
+ Project.where('EXISTS (?) OR projects.visibility_level IN (?)',
+ authorized,
+ levels)
+ end
+
+ # Builds a collection for an anonymous user.
+ def collection_without_user
+ if private_only? || owned_projects?
+ Project.none
+ else
+ Project.public_to_user
+ end
+ end
+
+ def owned_projects?
+ params[:owned].present?
+ end
- projects
+ def private_only?
+ params[:non_public].present?
end
def by_ids(items)
diff --git a/changelogs/unreleased/refactor-projects-finder-init-collection.yml b/changelogs/unreleased/refactor-projects-finder-init-collection.yml
new file mode 100644
index 00000000000..c8113419f21
--- /dev/null
+++ b/changelogs/unreleased/refactor-projects-finder-init-collection.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor ProjectsFinder#init_collection to produce more efficient queries for
+ retrieving projects
+merge_request:
+author:
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 2b53798e70f..3c6f05e41a1 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -13,18 +13,8 @@ module Gitlab
scope :public_and_internal_only, -> { where(visibility_level: [PUBLIC, INTERNAL] ) }
scope :non_public_only, -> { where.not(visibility_level: PUBLIC) }
- scope :public_to_user, -> (user) do
- if user
- if user.admin?
- all
- elsif !user.external?
- public_and_internal_only
- else
- public_only
- end
- else
- public_only
- end
+ scope :public_to_user, -> (user = nil) do
+ where(visibility_level: VisibilityLevel.levels_for_user(user))
end
end
@@ -35,6 +25,20 @@ module Gitlab
class << self
delegate :values, to: :options
+ def levels_for_user(user = nil)
+ if user
+ if user.admin?
+ [PRIVATE, INTERNAL, PUBLIC]
+ elsif !user.external?
+ [INTERNAL, PUBLIC]
+ else
+ [PUBLIC]
+ end
+ else
+ [PUBLIC]
+ end
+ end
+
def string_values
string_options.keys
end
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index 3255c6f1ef7..a8f21803ec7 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -18,4 +18,35 @@ describe Gitlab::VisibilityLevel, lib: true do
expect(described_class.level_value(100)).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
+
+ describe '.levels_for_user' do
+ it 'returns all levels for an admin' do
+ user = double(:user, admin?: true)
+
+ expect(described_class.levels_for_user(user)).
+ to eq([Gitlab::VisibilityLevel::PRIVATE,
+ Gitlab::VisibilityLevel::INTERNAL,
+ Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it 'returns INTERNAL and PUBLIC for internal users' do
+ user = double(:user, admin?: false, external?: false)
+
+ expect(described_class.levels_for_user(user)).
+ to eq([Gitlab::VisibilityLevel::INTERNAL,
+ Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it 'returns PUBLIC for external users' do
+ user = double(:user, admin?: false, external?: true)
+
+ expect(described_class.levels_for_user(user)).
+ to eq([Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it 'returns PUBLIC when no user is given' do
+ expect(described_class.levels_for_user).
+ to eq([Gitlab::VisibilityLevel::PUBLIC])
+ end
+ end
end