PERF: refactor user search so works more efficiently
Stop scanning entire user table
This commit is contained in:
parent
1eeed5fed2
commit
e074651fdc
|
@ -10,10 +10,29 @@ class UserSearch
|
||||||
@limit = opts[:limit] || 20
|
@limit = opts[:limit] || 20
|
||||||
end
|
end
|
||||||
|
|
||||||
def search
|
def scoped_users
|
||||||
users = User.order(User.sql_fragment("CASE WHEN username_lower = ? THEN 0 ELSE 1 END ASC", @term.downcase))
|
users = User.where("active")
|
||||||
|
|
||||||
users = users.where(active: true)
|
unless @searching_user && @searching_user.staff?
|
||||||
|
users = users.not_suspended
|
||||||
|
end
|
||||||
|
|
||||||
|
# Only show users who have access to private topic
|
||||||
|
if @topic_id && @topic_allowed_users == "true"
|
||||||
|
topic = Topic.find_by(id: @topic_id)
|
||||||
|
|
||||||
|
if topic.category && topic.category.read_restricted
|
||||||
|
users = users.includes(:secure_categories)
|
||||||
|
.where("users.admin = TRUE OR categories.id = ?", topic.category.id)
|
||||||
|
.references(:categories)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
users.limit(@limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
def filtered_by_term_users
|
||||||
|
users = scoped_users
|
||||||
|
|
||||||
if @term.present?
|
if @term.present?
|
||||||
if SiteSetting.enable_names?
|
if SiteSetting.enable_names?
|
||||||
|
@ -28,29 +47,53 @@ class UserSearch
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
users
|
||||||
|
end
|
||||||
|
|
||||||
|
def search_ids
|
||||||
|
users = Set.new
|
||||||
|
|
||||||
|
# 1. exact username matches
|
||||||
|
if @term.present?
|
||||||
|
scoped_users.where(username_lower: @term.downcase)
|
||||||
|
.pluck(:id)
|
||||||
|
.each{|id| users << id}
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return users.to_a if users.length == @limit
|
||||||
|
|
||||||
|
# 2. in topic
|
||||||
if @topic_id
|
if @topic_id
|
||||||
users = users.joins(User.sql_fragment("LEFT JOIN (SELECT DISTINCT p.user_id FROM POSTS p WHERE topic_id = ?) s ON s.user_id = users.id", @topic_id))
|
filtered_by_term_users.where('users.id in (SELECT p.user_id FROM posts p WHERE topic_id = ?)', @topic_id)
|
||||||
.order("CASE WHEN s.user_id IS NULL THEN 0 ELSE 1 END DESC")
|
.order('last_seen_at DESC')
|
||||||
|
.limit(@limit - users.length)
|
||||||
|
.pluck(:id)
|
||||||
|
.each{|id| users << id}
|
||||||
end
|
end
|
||||||
|
|
||||||
unless @searching_user && @searching_user.staff?
|
return users.to_a if users.length == @limit
|
||||||
users = users.not_suspended
|
|
||||||
end
|
|
||||||
|
|
||||||
# Only show users who have access to private topic
|
# 3. global matches
|
||||||
if @topic_id && @topic_allowed_users == "true"
|
filtered_by_term_users.order('last_seen_at DESC')
|
||||||
allowed_user_ids = []
|
.limit(@limit - users.length)
|
||||||
topic = Topic.find_by(id: @topic_id)
|
.pluck(:id)
|
||||||
|
.each{|id| users << id}
|
||||||
|
|
||||||
if topic.category && topic.category.read_restricted
|
users.to_a
|
||||||
users = users.includes(:secure_categories)
|
|
||||||
.where("users.admin = TRUE OR categories.id = ?", topic.category.id)
|
end
|
||||||
.references(:categories)
|
|
||||||
end
|
def search
|
||||||
end
|
|
||||||
|
ids = search_ids
|
||||||
|
return User.where("0=1") if ids.empty?
|
||||||
|
|
||||||
|
User.joins("JOIN (SELECT unnest uid, row_number() OVER () AS rn
|
||||||
|
FROM unnest('{#{ids.join(",")}}'::int[])
|
||||||
|
) x on uid = users.id")
|
||||||
|
.order("rn")
|
||||||
|
|
||||||
users.order("CASE WHEN last_seen_at IS NULL THEN 0 ELSE 1 END DESC, last_seen_at DESC, username ASC")
|
|
||||||
.limit(@limit)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,7 +38,7 @@ describe UserSearch do
|
||||||
# normal search
|
# normal search
|
||||||
results = search_for(user1.name.split(" ").first)
|
results = search_for(user1.name.split(" ").first)
|
||||||
expect(results.size).to eq(1)
|
expect(results.size).to eq(1)
|
||||||
expect(results.first).to eq(user1)
|
expect(results.first.username).to eq(user1.username)
|
||||||
|
|
||||||
# lower case
|
# lower case
|
||||||
results = search_for(user1.name.split(" ").first.downcase)
|
results = search_for(user1.name.split(" ").first.downcase)
|
||||||
|
@ -106,7 +106,7 @@ describe UserSearch do
|
||||||
|
|
||||||
# find an exact match first
|
# find an exact match first
|
||||||
results = search_for("mrB")
|
results = search_for("mrB")
|
||||||
expect(results.first).to eq(user1)
|
expect(results.first.username).to eq(user1.username)
|
||||||
|
|
||||||
# don't return inactive users
|
# don't return inactive users
|
||||||
results = search_for("Ghost")
|
results = search_for("Ghost")
|
||||||
|
|
Loading…
Reference in New Issue