diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 10:00:54 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 10:00:54 +0000 |
commit | 3cccd102ba543e02725d247893729e5c73b38295 (patch) | |
tree | f36a04ec38517f5deaaacb5acc7d949688d1e187 /lib/event_filter.rb | |
parent | 205943281328046ef7b4528031b90fbda70c75ac (diff) | |
download | gitlab-ce-3cccd102ba543e02725d247893729e5c73b38295.tar.gz |
Add latest changes from gitlab-org/gitlab@14-10-stable-eev14.10.0-rc42
Diffstat (limited to 'lib/event_filter.rb')
-rw-r--r-- | lib/event_filter.rb | 156 |
1 files changed, 152 insertions, 4 deletions
diff --git a/lib/event_filter.rb b/lib/event_filter.rb index 915ab355508..8833207dd1d 100644 --- a/lib/event_filter.rb +++ b/lib/event_filter.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# rubocop: disable CodeReuse/ActiveRecord class EventFilter include Gitlab::Utils::StrongMemoize @@ -24,7 +25,6 @@ class EventFilter filter == key.to_s end - # rubocop: disable CodeReuse/ActiveRecord def apply_filter(events) case filter when PUSH @@ -34,9 +34,9 @@ class EventFilter when COMMENTS events.commented_action when TEAM - events.where(action: [:joined, :left, :expired]) + events.where(action: Event::TEAM_ACTIONS) when ISSUE - events.where(action: [:created, :updated, :closed, :reopened], target_type: 'Issue') + events.where(action: Event::ISSUE_ACTIONS, target_type: 'Issue') when WIKI wiki_events(events) when DESIGNS @@ -45,10 +45,157 @@ class EventFilter events end end - # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable Metrics/CyclomaticComplexity + # This method build specialized in-operator optimized queries based on different + # filter parameters. All queries will benefit from the index covering the following columns: + # author_id target_type action id + # + # More context: https://docs.gitlab.com/ee/development/database/efficient_in_operator_queries.html#the-inoperatoroptimization-module + def in_operator_query_builder_params(user_ids) + case filter + when ALL + in_operator_params(array_scope_ids: user_ids) + when PUSH + # Here we need to add an order hint column to force the correct index usage. + # Without the order hint, the following conditions will use the `index_events_on_author_id_and_id` + # index which is not as efficient as the `index_events_for_followed_users` index. + # > target_type IS NULL AND action = 5 AND author_id = X ORDER BY id DESC + # + # The order hint adds an extra order by column which doesn't affect the result but forces the planner + # to use the correct index: + # > target_type IS NULL AND action = 5 AND author_id = X ORDER BY target_type DESC, id DESC + in_operator_params( + array_scope_ids: user_ids, + scope: Event.where(target_type: nil).pushed_action, + order_hint_column: :target_type + ) + when MERGED + in_operator_params( + array_scope_ids: user_ids, + scope: Event.where(target_type: MergeRequest.to_s).merged_action + ) + when COMMENTS + in_operator_params( + array_scope_ids: user_ids, + scope: Event.commented_action, + in_column: :target_type, + in_values: [Note, *Note.descendants].map(&:name) # To make the query efficient we need to list all Note classes + ) + when TEAM + in_operator_params( + array_scope_ids: user_ids, + scope: Event.where(target_type: nil), + order_hint_column: :target_type, + in_column: :action, + in_values: Event.actions.values_at(*Event::TEAM_ACTIONS) + ) + when ISSUE + in_operator_params( + array_scope_ids: user_ids, + scope: Event.where(target_type: Issue.name), + in_column: :action, + in_values: Event.actions.values_at(*Event::ISSUE_ACTIONS) + ) + when WIKI + in_operator_params( + array_scope_ids: user_ids, + scope: Event.for_wiki_page, + in_column: :action, + in_values: Event.actions.values_at(*Event::WIKI_ACTIONS) + ) + when DESIGNS + in_operator_params( + array_scope_ids: user_ids, + scope: Event.for_design, + in_column: :action, + in_values: Event.actions.values_at(*Event::DESIGN_ACTIONS) + ) + else + in_operator_params(array_scope_ids: user_ids) + end + end + # rubocop: enable Metrics/CyclomaticComplexity private + def in_operator_params(array_scope_ids:, scope: nil, in_column: nil, in_values: nil, order_hint_column: nil) + base_scope = Event.all + base_scope = base_scope.merge(scope) if scope + + order = { id: :desc } + finder_query = -> (id_expression) { Event.where(Event.arel_table[:id].eq(id_expression)) } + + if order_hint_column.present? + order = Gitlab::Pagination::Keyset::Order.build([ + Gitlab::Pagination::Keyset::ColumnOrderDefinition.new( + attribute_name: order_hint_column, + order_expression: Event.arel_table[order_hint_column].desc, + nullable: :nulls_last, + distinct: false + ), + Gitlab::Pagination::Keyset::ColumnOrderDefinition.new( + attribute_name: :id, + order_expression: Event.arel_table[:id].desc + ) + ]) + + finder_query = -> (_order_hint, id_expression) { Event.where(Event.arel_table[:id].eq(id_expression)) } + end + + base_scope = base_scope.reorder(order) + + array_params = in_operator_array_params( + array_scope_ids: array_scope_ids, + scope: base_scope, + in_column: in_column, + in_values: in_values + ) + + array_params.merge( + scope: base_scope, + finder_query: finder_query + ) + end + + # This method builds the array_ parameters + # without in_column parameter: uses one IN filter: author_id + # with in_column: two IN filters: author_id, (target_type OR action) + def in_operator_array_params(scope:, array_scope_ids:, in_column: nil, in_values: nil) + if in_column + # Builds Carthesian product of the in_values and the array_scope_ids (in this case: user_ids). + # The process is described here: https://docs.gitlab.com/ee/development/database/efficient_in_operator_queries.html#multiple-in-queries + # VALUES ((array_scope_ids[0], in_values[0]), (array_scope_ids[1], in_values[0]) ...) + cartesian = array_scope_ids.product(in_values) + user_with_column_list = Arel::Nodes::ValuesList.new(cartesian) + + as = "array_ids(id, #{Event.connection.quote_column_name(in_column)})" + from = Arel::Nodes::Grouping.new(user_with_column_list).as(as) + { + array_scope: User.select(:id, in_column).from(from), + array_mapping_scope: -> (author_id_expression, in_column_expression) do + Event + .merge(scope) + .where(Event.arel_table[:author_id].eq(author_id_expression)) + .where(Event.arel_table[in_column].eq(in_column_expression)) + end + } + else + # Builds a simple query to represent the array_scope_ids + # VALUES ((array_scope_ids[0]), (array_scope_ids[2])...) + array_ids_list = Arel::Nodes::ValuesList.new(array_scope_ids.map { |id| [id] }) + from = Arel::Nodes::Grouping.new(array_ids_list).as('array_ids(id)') + { + array_scope: User.select(:id).from(from), + array_mapping_scope: -> (author_id_expression) do + Event + .merge(scope) + .where(Event.arel_table[:author_id].eq(author_id_expression)) + end + } + end + end + def wiki_events(events) events.for_wiki_page end @@ -61,5 +208,6 @@ class EventFilter [ALL, PUSH, MERGED, ISSUE, COMMENTS, TEAM, WIKI, DESIGNS] end end +# rubocop: enable CodeReuse/ActiveRecord EventFilter.prepend_mod_with('EventFilter') |