diff --git a/app/logical/post_query_builder.rb b/app/logical/post_query_builder.rb
index 8d7bd0b8b..f386432b9 100644
--- a/app/logical/post_query_builder.rb
+++ b/app/logical/post_query_builder.rb
@@ -85,6 +85,28 @@ class PostQueryBuilder
relation
end
+ def table_for_metatag(metatag)
+ if metatag.in?(Tag::COUNT_METATAGS)
+ metatag[/(?
[a-z]+)_count\z/i, :table]
+ else
+ nil
+ end
+ end
+
+ def tables_for_query(q)
+ metatags = q.keys
+ metatags << q[:order].remove(/_(asc|desc)\z/i) if q[:order].present?
+
+ tables = metatags.map { |metatag| table_for_metatag(metatag.to_s) }
+ tables.compact.uniq
+ end
+
+ def add_joins(q, relation)
+ tables = tables_for_query(q)
+ relation = relation.with_stats(tables)
+ relation
+ end
+
def hide_deleted_posts?(q)
return false if CurrentUser.admin_mode?
return false if q[:status].in?(%w[deleted active any all])
@@ -107,6 +129,7 @@ class PostQueryBuilder
relation = relation.where("posts.rating = 's'")
end
+ relation = add_joins(q, relation)
relation = add_range_relation(q[:post_id], "posts.id", relation)
relation = add_range_relation(q[:mpixels], "posts.image_width * posts.image_height / 1000000.0", relation)
relation = add_range_relation(q[:ratio], "ROUND(1.0 * posts.image_width / GREATEST(1, posts.image_height), 2)", relation)
@@ -122,6 +145,10 @@ class PostQueryBuilder
end
relation = add_range_relation(q[:post_tag_count], "posts.tag_count", relation)
+ Tag::COUNT_METATAGS.each do |column|
+ relation = add_range_relation(q[column.to_sym], "posts.#{column}", relation)
+ end
+
if q[:md5]
relation = relation.where("posts.md5": q[:md5])
end
@@ -544,6 +571,11 @@ class PostQueryBuilder
when "filesize_asc"
relation = relation.order("posts.file_size ASC")
+ when /\A(?#{Tag::COUNT_METATAGS.join("|")})(_(?asc|desc))?\z/i
+ column = $~[:column]
+ direction = $~[:direction] || "desc"
+ relation = relation.order(column => direction, :id => direction)
+
when "tagcount", "tagcount_desc"
relation = relation.order("posts.tag_count DESC")
diff --git a/app/models/post.rb b/app/models/post.rb
index cd206825a..c928ac07f 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -1609,6 +1609,33 @@ class Post < ApplicationRecord
joins("CROSS JOIN unnest(string_to_array(tag_string, ' ')) AS tag")
end
+ def with_comment_stats
+ relation = left_outer_joins(:comments).group(:id).select("posts.*")
+ relation = relation.select("COUNT(comments.id) AS comment_count")
+ relation = relation.select("COUNT(comments.id) FILTER (WHERE comments.is_deleted = TRUE) AS deleted_comment_count")
+ relation = relation.select("COUNT(comments.id) FILTER (WHERE comments.is_deleted = FALSE) AS active_comment_count")
+ relation
+ end
+
+ def with_note_stats
+ relation = left_outer_joins(:notes).group(:id).select("posts.*")
+ relation = relation.select("COUNT(notes.id) AS note_count")
+ relation = relation.select("COUNT(notes.id) FILTER (WHERE notes.is_active = TRUE) AS active_note_count")
+ relation = relation.select("COUNT(notes.id) FILTER (WHERE notes.is_active = FALSE) AS deleted_note_count")
+ relation
+ end
+
+ def with_stats(tables)
+ return all if tables.empty?
+
+ relation = all
+ tables.each do |table|
+ relation = relation.send("with_#{table}_stats")
+ end
+
+ from(relation.arel.as("posts"))
+ end
+
def pending
where(is_pending: true)
end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 26519fff8..1a2176d33 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -1,5 +1,9 @@
class Tag < ApplicationRecord
COSINE_SIMILARITY_RELATED_TAG_THRESHOLD = 300
+ COUNT_METATAGS = %w[
+ comment_count deleted_comment_count active_comment_count
+ note_count deleted_note_count active_note_count
+ ]
METATAGS = %w[
-user user -approver approver commenter comm noter noteupdater artcomm
-pool pool ordpool -favgroup favgroup -fav fav ordfav md5 -rating rating
@@ -7,7 +11,7 @@ class Tag < ApplicationRecord
-source id -id date age order limit -status status tagcount parent -parent
child pixiv_id pixiv search upvote downvote filetype -filetype flagger
-flagger appealer -appealer disapproval -disapproval
- ] + TagCategory.short_name_list.map {|x| "#{x}tags"}
+ ] + TagCategory.short_name_list.map {|x| "#{x}tags"} + COUNT_METATAGS
SUBQUERY_METATAGS = %w[commenter comm noter noteupdater artcomm flagger -flagger appealer -appealer]
@@ -790,6 +794,9 @@ class Tag < ApplicationRecord
q[:downvote] = User.name_to_id(g2)
end
+ when *COUNT_METATAGS
+ q[g1.to_sym] = parse_helper(g2)
+
end
else
diff --git a/test/unit/post_test.rb b/test/unit/post_test.rb
index 2af27e446..1e67ecf80 100644
--- a/test/unit/post_test.rb
+++ b/test/unit/post_test.rb
@@ -2103,6 +2103,16 @@ class PostTest < ActiveSupport::TestCase
assert_tag_match([posts[1]], "noter:none")
end
+ should "return posts for the note_count: metatag" do
+ posts = FactoryBot.create_list(:post, 3)
+ FactoryBot.create(:note, post: posts[0], is_active: true)
+ FactoryBot.create(:note, post: posts[1], is_active: false)
+
+ assert_tag_match([posts[1], posts[0]], "note_count:1")
+ assert_tag_match([posts[0]], "active_note_count:1")
+ assert_tag_match([posts[1]], "deleted_note_count:1")
+ end
+
should "return posts for the artcomm: metatag" do
users = FactoryBot.create_list(:user, 2)
posts = FactoryBot.create_list(:post, 2)
@@ -2387,6 +2397,8 @@ class PostTest < ActiveSupport::TestCase
p
end
+ FactoryBot.create(:note, post: posts.second)
+
assert_tag_match(posts.reverse, "order:id_desc")
assert_tag_match(posts.reverse, "order:score")
assert_tag_match(posts.reverse, "order:favcount")
@@ -2404,6 +2416,8 @@ class PostTest < ActiveSupport::TestCase
assert_tag_match(posts.reverse, "order:chartags")
assert_tag_match(posts.reverse, "order:copytags")
assert_tag_match(posts.reverse, "order:rank")
+ assert_tag_match(posts.reverse, "order:note_count")
+ assert_tag_match(posts.reverse, "order:note_count_desc")
assert_tag_match(posts, "order:id_asc")
assert_tag_match(posts, "order:score_asc")
@@ -2421,6 +2435,7 @@ class PostTest < ActiveSupport::TestCase
assert_tag_match(posts, "order:arttags_asc")
assert_tag_match(posts, "order:chartags_asc")
assert_tag_match(posts, "order:copytags_asc")
+ assert_tag_match(posts, "order:note_count_asc")
end
should "return posts for order:comment_bumped" do