FIX: Select earliest post when aggregating posts in a topic for search.

This is a revert of
d8c796bc44
and
5bf0a0893b.

Linking to the post within a topic that has the highest rank was
confusing users and hard to explain because ranking is determined via
the PG ranking function. See the following meta topics for the
complaints after we switch to the new ordering:

1. https://meta.discourse.org/t/title-search-not-working-as-expected/157737
2. https://meta.discourse.org/t/search-results-should-prioritize-first-post-in-topic-when-title-matches-search-term/175154
This commit is contained in:
Alan Guo Xiang Tan 2021-02-01 13:40:06 +08:00
parent 91fb298510
commit 4b3f65bb26
2 changed files with 49 additions and 87 deletions

View File

@ -986,53 +986,30 @@ class Search
posts posts
end end
if aggregate_search
aggregate_relation = Post.unscoped
.select("subquery.topic_id id")
.group("subquery.topic_id")
posts = posts.select(posts.arel.projections)
end
if @order == :latest if @order == :latest
posts = posts.reorder("posts.created_at DESC")
if aggregate_search if aggregate_search
aggregate_relation = aggregate_relation posts = posts.order("MAX(posts.created_at) DESC")
.select( else
"MAX(subquery.post_number) post_number", posts = posts.reorder("posts.created_at DESC")
"MAX(subquery.created_at) created_at"
)
.order("created_at DESC")
end end
elsif @order == :latest_topic elsif @order == :latest_topic
posts = posts.order("topics.created_at DESC")
if aggregate_search if aggregate_search
posts = posts.select("topics.created_at topic_created_at") posts = posts.order("MAX(topics.created_at) DESC")
else
aggregate_relation = aggregate_relation posts = posts.order("topics.created_at DESC")
.select(
"MIN(subquery.post_number) post_number",
"MAX(subquery.topic_created_at) topic_created_at"
)
.order("topic_created_at DESC")
end end
elsif @order == :views elsif @order == :views
posts = posts.order("topics.views DESC")
if aggregate_search if aggregate_search
posts = posts.select("topics.views topic_views") posts = posts.order("MAX(topics.views) DESC")
else
aggregate_relation = aggregate_relation posts = posts.order("topics.views DESC")
.select(
"MIN(subquery.post_number) post_number",
"MAX(subquery.topic_views) topic_views"
)
.order("topic_views DESC")
end end
elsif @order == :likes elsif @order == :likes
posts = posts.order("posts.like_count DESC") if aggregate_search
posts = posts.order("MAX(posts.like_count) DESC")
else
posts = posts.order("posts.like_count DESC")
end
else else
rank = <<~SQL rank = <<~SQL
TS_RANK_CD( TS_RANK_CD(
@ -1069,31 +1046,22 @@ class Search
"(#{rank} * #{category_priority_weights})" "(#{rank} * #{category_priority_weights})"
end end
if aggregate_search posts =
posts = posts.select("#{data_ranking} rank", "topics.bumped_at topic_bumped_at") if aggregate_search
.order("rank DESC", "topic_bumped_at DESC") posts.order("MAX(#{data_ranking}) DESC")
else
posts.order("#{data_ranking} DESC")
end
aggregate_relation = aggregate_relation posts = posts.order("topics.bumped_at DESC")
.select( end
"(ARRAY_AGG(subquery.post_number ORDER BY subquery.rank DESC, subquery.topic_bumped_at DESC))[1] post_number",
"MAX(subquery.rank) rank", "MAX(subquery.topic_bumped_at) topic_bumped_at" posts =
) if secure_category_ids.present?
.order("rank DESC", "topic_bumped_at DESC") posts.where("(categories.id IS NULL) OR (NOT categories.read_restricted) OR (categories.id IN (?))", secure_category_ids).references(:categories)
else else
posts = posts.order("#{data_ranking} DESC", "topics.bumped_at DESC") posts.where("(categories.id IS NULL) OR (NOT categories.read_restricted)").references(:categories)
end end
end
if secure_category_ids.present?
posts = posts.where("(categories.id IS NULL) OR (NOT categories.read_restricted) OR (categories.id IN (?))", secure_category_ids).references(:categories)
else
posts = posts.where("(categories.id IS NULL) OR (NOT categories.read_restricted)").references(:categories)
end
if aggregate_search
posts = yield(posts) if block_given?
posts = aggregate_relation.from(posts)
end
if @order if @order
advanced_order = Search.advanced_orders&.fetch(@order, nil) advanced_order = Search.advanced_orders&.fetch(@order, nil)
@ -1169,36 +1137,28 @@ class Search
Search.min_post_id Search.min_post_id
end end
if @order == :likes min_or_max = @order == :latest ? "max" : "min"
# likes are a pain to aggregate so skip
query = posts_query(limit, **default_opts).select('topics.id', 'posts.post_number')
if min_id > 0 query =
low_set = query.dup.where("post_search_data.post_id < #{min_id}") if @order == :likes
high_set = query.where("post_search_data.post_id >= #{min_id}") # likes are a pain to aggregate so skip
posts_query(limit, type_filter: opts[:type_filter])
{ default: wrap_rows(high_set), remaining: wrap_rows(low_set) } .select('topics.id', "posts.post_number")
else else
{ default: wrap_rows(query) } posts_query(limit, aggregate_search: true, type_filter: opts[:type_filter])
end .select('topics.id', "#{min_or_max}(posts.post_number) post_number")
else .group('topics.id')
query = posts_query(limit, **default_opts, aggregate_search: true) do |posts|
if min_id > 0
posts.select("post_search_data.post_id post_search_data_post_id")
else
posts
end
end end
if min_id > 0 if min_id > 0
low_set = query.dup.where("subquery.post_search_data_post_id < #{min_id}") low_set = query.dup.where("post_search_data.post_id < #{min_id}")
high_set = query.where("subquery.post_search_data_post_id >= #{min_id}") high_set = query.where("post_search_data.post_id >= #{min_id}")
{ default: wrap_rows(high_set), remaining: wrap_rows(low_set) } return { default: wrap_rows(high_set), remaining: wrap_rows(low_set) }
else
{ default: wrap_rows(query) }
end
end end
# double wrapping so we get correct row numbers
{ default: wrap_rows(query) }
end end
def aggregate_posts(post_sql) def aggregate_posts(post_sql)

View File

@ -601,8 +601,8 @@ describe Search do
expect(result.posts.pluck(:id)).to eq([post2.id, post.id]) expect(result.posts.pluck(:id)).to eq([post2.id, post.id])
end end
it 'aggregates searches in a topic by returning the post with the highest rank' do it 'aggregates searches in a topic by returning the post with the lowest post number' do
_post = Fabricate(:post, topic: topic, raw: "this is a play post") post = Fabricate(:post, topic: topic, raw: "this is a play post")
post2 = Fabricate(:post, topic: topic, raw: "play play playing played play") post2 = Fabricate(:post, topic: topic, raw: "play play playing played play")
post3 = Fabricate(:post, raw: "this is a play post") post3 = Fabricate(:post, raw: "this is a play post")
@ -613,7 +613,7 @@ describe Search do
results = Search.execute('play') results = Search.execute('play')
expect(results.posts.map(&:id)).to eq([ expect(results.posts.map(&:id)).to eq([
post2.id, post.id,
post3.id post3.id
]) ])
end end
@ -1892,9 +1892,11 @@ describe Search do
it 'allows to define custom order' do it 'allows to define custom order' do
expect(Search.new("advanced").execute.posts).to eq([post1, post0]) expect(Search.new("advanced").execute.posts).to eq([post1, post0])
Search.advanced_order(:chars) do |posts| Search.advanced_order(:chars) do |posts|
posts.reorder("(SELECT LENGTH(raw) FROM posts WHERE posts.topic_id = subquery.topic_id) DESC") posts.reorder("MAX(LENGTH(posts.raw)) DESC")
end end
expect(Search.new("advanced order:chars").execute.posts).to eq([post0, post1]) expect(Search.new("advanced order:chars").execute.posts).to eq([post0, post1])
end end
end end