mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-03 18:12:45 +08:00
`SiteSerializer#post_action_types` and `SiteSerializer#topic_flag_types` both call the `Flag.used_flag_ids` method which executes queries that becomes very slow on sites with alot of records in either the `post_actions` or `reviewable_scores` table. On the `post_actions` table, we execute `SELECT DISTINCT post_action_type_id FROM post_actions`. On the `reviewable_scores` table, we execute `SELECT DISTINCT reviewable_scope_type FROM reviewable_scopes`. The problem with both queries is that it requires the PG planner to scan through every single row in those tables. For our use case, all we actually need is to check if a flag is being referenced by a record in either the `post_actions` or `reviewable_scores` tables. This commit updates the `Flag.used_flag_ids` to accept a `flag_ids` argument and use the argument to check whether the flag ids are referenced in the `post_action_type_id` or `reviewable_scope_type` foreign keys.
94 lines
2.7 KiB
Ruby
94 lines
2.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class Flag < ActiveRecord::Base
|
|
# TODO(2025-01-15): krisk remove
|
|
self.ignored_columns = ["custom_type"]
|
|
|
|
DEFAULT_VALID_APPLIES_TO = %w[Post Topic]
|
|
MAX_SYSTEM_FLAG_ID = 1000
|
|
MAX_NAME_LENGTH = 200
|
|
MAX_DESCRIPTION_LENGTH = 1000
|
|
scope :enabled, -> { where(enabled: true) }
|
|
scope :system, -> { where("id < 1000") }
|
|
scope :custom, -> { where("id >= 1000") }
|
|
|
|
before_save :set_position
|
|
before_save :set_name_key
|
|
after_commit { reset_flag_settings! if !skip_reset_flag_callback }
|
|
|
|
attr_accessor :skip_reset_flag_callback
|
|
|
|
default_scope do
|
|
order(:position).where(score_type: false).where.not(id: PostActionType::LIKE_POST_ACTION_ID)
|
|
end
|
|
|
|
def used?
|
|
PostAction.exists?(post_action_type_id: self.id) ||
|
|
ReviewableScore.exists?(reviewable_score_type: self.id)
|
|
end
|
|
|
|
def self.valid_applies_to_types
|
|
Set.new(DEFAULT_VALID_APPLIES_TO | DiscoursePluginRegistry.flag_applies_to_types)
|
|
end
|
|
|
|
def self.reset_flag_settings!
|
|
# Flags are cached in Redis for better performance. After the update,
|
|
# we need to reload them in all processes.
|
|
PostActionType.reload_types
|
|
end
|
|
|
|
def self.used_flag_ids(flag_ids)
|
|
sql =
|
|
flag_ids
|
|
.reduce([]) do |queries, flag_id|
|
|
queries << "(SELECT #{flag_id} FROM post_actions WHERE post_action_type_id = #{flag_id} LIMIT 1)"
|
|
queries << "(SELECT #{flag_id} FROM reviewable_scores WHERE reviewable_score_type = #{flag_id} LIMIT 1)"
|
|
queries
|
|
end
|
|
.join(" UNION ")
|
|
|
|
DB.query_single(sql)
|
|
end
|
|
|
|
def system?
|
|
self.id.present? && self.id < MAX_SYSTEM_FLAG_ID
|
|
end
|
|
|
|
def applies_to?(type)
|
|
self.applies_to.include?(type)
|
|
end
|
|
|
|
private
|
|
|
|
def reset_flag_settings!
|
|
self.class.reset_flag_settings!
|
|
end
|
|
|
|
def set_position
|
|
self.position = Flag.maximum(:position).to_i + 1 if !self.position
|
|
end
|
|
|
|
def set_name_key
|
|
prefix = self.system? ? "" : "custom_"
|
|
self.name_key = "#{prefix}#{self.name.squeeze(" ").gsub(" ", "_").gsub(/[^\w]/, "").downcase}"
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: flags
|
|
#
|
|
# id :bigint not null, primary key
|
|
# name :string
|
|
# name_key :string
|
|
# description :text
|
|
# notify_type :boolean default(FALSE), not null
|
|
# auto_action_type :boolean default(FALSE), not null
|
|
# require_message :boolean default(FALSE), not null
|
|
# applies_to :string not null, is an Array
|
|
# position :integer not null
|
|
# enabled :boolean default(TRUE), not null
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# score_type :boolean default(FALSE), not null
|
|
#
|