search: fix various metatag search issues.
* Support negating the child: and embedded: metatags. * Fix approver:<any|none>, disapproved:<reason>, commentary:<type> being case sensitive. * Fix child:garbage, locked:garbage, embedded:garbage returning all posts instead of no posts. * Fix not being able to use source:, locked:, or -id: twice in the same search.
This commit is contained in:
@@ -38,7 +38,9 @@ class PostQueryBuilder
|
||||
-filetype filetype
|
||||
-disapproved disapproved
|
||||
-parent parent
|
||||
-child child
|
||||
-search search
|
||||
-embedded embedded
|
||||
md5
|
||||
width
|
||||
height
|
||||
@@ -52,9 +54,7 @@ class PostQueryBuilder
|
||||
order
|
||||
limit
|
||||
tagcount
|
||||
child
|
||||
pixiv_id pixiv
|
||||
embedded
|
||||
] + TagCategory.short_name_list.map {|x| "#{x}tags"} + COUNT_METATAGS + COUNT_METATAG_SYNONYMS
|
||||
|
||||
ORDER_METATAGS = %w[
|
||||
@@ -99,9 +99,10 @@ class PostQueryBuilder
|
||||
end
|
||||
|
||||
def user_matches(field, username)
|
||||
if username == "any"
|
||||
case username.downcase
|
||||
when "any"
|
||||
Post.where.not(field => nil)
|
||||
elsif username == "none"
|
||||
when "none"
|
||||
Post.where(field => nil)
|
||||
else
|
||||
Post.where(field => User.name_matches(username))
|
||||
@@ -188,13 +189,56 @@ class PostQueryBuilder
|
||||
end
|
||||
end
|
||||
|
||||
def disapproved_matches(query)
|
||||
if query.downcase.in?(PostDisapproval::REASONS)
|
||||
Post.where(disapprovals: PostDisapproval.where(reason: query.downcase))
|
||||
elsif User.normalize_name(query) == CurrentUser.user.name
|
||||
Post.where(disapprovals: PostDisapproval.where(user: CurrentUser.user))
|
||||
else
|
||||
Post.none
|
||||
end
|
||||
end
|
||||
|
||||
def parent_matches(parent)
|
||||
if parent.downcase == "none"
|
||||
case parent.downcase
|
||||
when "none"
|
||||
Post.where(parent: nil)
|
||||
elsif parent.downcase == "any"
|
||||
when "any"
|
||||
Post.where.not(parent: nil)
|
||||
elsif parent
|
||||
when /\A\d+\z/
|
||||
Post.where(id: parent).or(Post.where(parent: parent))
|
||||
else
|
||||
Post.none
|
||||
end
|
||||
end
|
||||
|
||||
def child_matches(child)
|
||||
case child.downcase
|
||||
when "none"
|
||||
Post.where(has_children: false)
|
||||
when "any"
|
||||
Post.where(has_children: true)
|
||||
else
|
||||
Post.none
|
||||
end
|
||||
end
|
||||
|
||||
def source_matches(source)
|
||||
case source.downcase
|
||||
when "none"
|
||||
Post.where_like(:source, "")
|
||||
else
|
||||
Post.where_ilike(:source, source + "*")
|
||||
end
|
||||
end
|
||||
|
||||
def embedded_matches(embedded)
|
||||
if embedded.truthy?
|
||||
Post.bit_flags_match(:has_embedded_notes, true)
|
||||
elsif embedded.falsy?
|
||||
Post.bit_flags_match(:has_embedded_notes, false)
|
||||
else
|
||||
Post.none
|
||||
end
|
||||
end
|
||||
|
||||
@@ -215,8 +259,19 @@ class PostQueryBuilder
|
||||
end
|
||||
end
|
||||
|
||||
def ordpool_matches(pool_name)
|
||||
# XXX unify with Pool#posts
|
||||
pool_posts = Pool.named(pool_name).joins("CROSS JOIN unnest(pools.post_ids) WITH ORDINALITY AS row(post_id, pool_index)").select(:post_id, :pool_index)
|
||||
Post.joins("JOIN (#{pool_posts.to_sql}) pool_posts ON pool_posts.post_id = posts.id").order("pool_posts.pool_index ASC")
|
||||
end
|
||||
|
||||
def favgroup_matches(query)
|
||||
favgroup = FavoriteGroup.visible(CurrentUser.user).name_or_id_matches(query, CurrentUser.user)
|
||||
Post.where(id: favgroup.select("unnest(post_ids)"))
|
||||
end
|
||||
|
||||
def commentary_matches(query)
|
||||
case query
|
||||
case query.downcase
|
||||
when "none", "false"
|
||||
Post.where.not(artist_commentary: ArtistCommentary.all).or(Post.where(artist_commentary: ArtistCommentary.deleted))
|
||||
when "any", "true"
|
||||
@@ -230,6 +285,19 @@ class PostQueryBuilder
|
||||
end
|
||||
end
|
||||
|
||||
def locked_matches(query)
|
||||
case query.downcase
|
||||
when "rating"
|
||||
Post.where(is_rating_locked: true)
|
||||
when "note", "notes"
|
||||
Post.where(is_note_locked: true)
|
||||
when "status"
|
||||
Post.where(is_status_locked: true)
|
||||
else
|
||||
Post.none
|
||||
end
|
||||
end
|
||||
|
||||
def table_for_metatag(metatag)
|
||||
if metatag.in?(COUNT_METATAGS)
|
||||
metatag[/(?<table>[a-z]+)_count\z/i, :table]
|
||||
@@ -310,20 +378,12 @@ class PostQueryBuilder
|
||||
relation = relation.merge(status_matches(query).negate)
|
||||
end
|
||||
|
||||
if q[:source]
|
||||
if q[:source] == "none"
|
||||
relation = relation.where_like(:source, '')
|
||||
else
|
||||
relation = relation.where_ilike(:source, q[:source].downcase + "*")
|
||||
end
|
||||
q[:source].to_a.each do |query|
|
||||
relation = relation.merge(source_matches(query))
|
||||
end
|
||||
|
||||
if q[:source_neg]
|
||||
if q[:source_neg] == "none"
|
||||
relation = relation.where_not_like(:source, '')
|
||||
else
|
||||
relation = relation.where_not_ilike(:source, q[:source_neg].downcase + "*")
|
||||
end
|
||||
q[:source_neg].to_a.each do |query|
|
||||
relation = relation.merge(source_matches(query).negate)
|
||||
end
|
||||
|
||||
q[:pool_neg].to_a.each do |pool_name|
|
||||
@@ -366,28 +426,12 @@ class PostQueryBuilder
|
||||
relation = relation.merge(user_matches(:approver, username))
|
||||
end
|
||||
|
||||
if q[:disapproved]
|
||||
q[:disapproved].each do |disapproved|
|
||||
if disapproved == CurrentUser.name
|
||||
disapprovals = CurrentUser.user.post_disapprovals.select(:post_id)
|
||||
else
|
||||
disapprovals = PostDisapproval.where(reason: disapproved)
|
||||
end
|
||||
|
||||
relation = relation.where("posts.id": disapprovals.select(:post_id))
|
||||
end
|
||||
q[:disapproved_neg].to_a.each do |query|
|
||||
relation = relation.merge(disapproved_matches(query).negate)
|
||||
end
|
||||
|
||||
if q[:disapproved_neg]
|
||||
q[:disapproved_neg].each do |disapproved|
|
||||
if disapproved == CurrentUser.name
|
||||
disapprovals = CurrentUser.user.post_disapprovals.select(:post_id)
|
||||
else
|
||||
disapprovals = PostDisapproval.where(reason: disapproved)
|
||||
end
|
||||
|
||||
relation = relation.where.not("posts.id": disapprovals.select(:post_id))
|
||||
end
|
||||
q[:disapproved].to_a.each do |query|
|
||||
relation = relation.merge(disapproved_matches(query))
|
||||
end
|
||||
|
||||
q[:flagger_neg].to_a.each do |username|
|
||||
@@ -438,8 +482,8 @@ class PostQueryBuilder
|
||||
relation = relation.merge(user_subquery_matches(ArtistCommentaryVersion.unscoped, username, field: :updater))
|
||||
end
|
||||
|
||||
if q[:post_id_negated]
|
||||
relation = relation.where("posts.id <> ?", q[:post_id_negated])
|
||||
q[:id_neg].to_a.each do |id|
|
||||
relation = relation.where.not(id: id)
|
||||
end
|
||||
|
||||
q[:parent].to_a.each do |parent|
|
||||
@@ -450,10 +494,12 @@ class PostQueryBuilder
|
||||
relation = relation.merge(parent_matches(parent_neg).negate)
|
||||
end
|
||||
|
||||
if q[:child] == "none"
|
||||
relation = relation.where("posts.has_children = FALSE")
|
||||
elsif q[:child] == "any"
|
||||
relation = relation.where("posts.has_children = TRUE")
|
||||
q[:child].to_a.each do |child|
|
||||
relation = relation.merge(child_matches(child))
|
||||
end
|
||||
|
||||
q[:child_neg].to_a.each do |child|
|
||||
relation = relation.merge(child_matches(child).negate)
|
||||
end
|
||||
|
||||
q[:rating].to_a.each do |rating|
|
||||
@@ -464,44 +510,32 @@ class PostQueryBuilder
|
||||
relation = relation.where.not(rating: rating.first.downcase)
|
||||
end
|
||||
|
||||
if q[:locked] == "rating"
|
||||
relation = relation.where("posts.is_rating_locked = TRUE")
|
||||
elsif q[:locked] == "note" || q[:locked] == "notes"
|
||||
relation = relation.where("posts.is_note_locked = TRUE")
|
||||
elsif q[:locked] == "status"
|
||||
relation = relation.where("posts.is_status_locked = TRUE")
|
||||
q[:locked].to_a.each do |lock|
|
||||
relation = relation.merge(locked_matches(lock))
|
||||
end
|
||||
|
||||
if q[:locked_negated] == "rating"
|
||||
relation = relation.where("posts.is_rating_locked = FALSE")
|
||||
elsif q[:locked_negated] == "note" || q[:locked_negated] == "notes"
|
||||
relation = relation.where("posts.is_note_locked = FALSE")
|
||||
elsif q[:locked_negated] == "status"
|
||||
relation = relation.where("posts.is_status_locked = FALSE")
|
||||
q[:locked_neg].to_a.each do |lock|
|
||||
relation = relation.merge(locked_matches(lock).negate)
|
||||
end
|
||||
|
||||
if q[:embedded].to_s.truthy?
|
||||
relation = relation.bit_flags_match(:has_embedded_notes, true)
|
||||
elsif q[:embedded].to_s.falsy?
|
||||
relation = relation.bit_flags_match(:has_embedded_notes, false)
|
||||
q[:embedded].to_a.each do |lock|
|
||||
relation = relation.merge(embedded_matches(lock))
|
||||
end
|
||||
|
||||
if q[:ordpool].present?
|
||||
pool_name = q[:ordpool]
|
||||
q[:embedded_neg].to_a.each do |lock|
|
||||
relation = relation.merge(embedded_matches(lock).negate)
|
||||
end
|
||||
|
||||
# XXX unify with Pool#posts
|
||||
pool_posts = Pool.named(pool_name).joins("CROSS JOIN unnest(pools.post_ids) WITH ORDINALITY AS row(post_id, pool_index)").select(:post_id, :pool_index)
|
||||
relation = relation.joins("JOIN (#{pool_posts.to_sql}) pool_posts ON pool_posts.post_id = posts.id").order("pool_posts.pool_index ASC")
|
||||
q[:ordpool].to_a.each do |pool_name|
|
||||
relation = relation.merge(ordpool_matches(pool_name))
|
||||
end
|
||||
|
||||
q[:favgroup_neg].to_a.each do |favgroup_name|
|
||||
favgroup = FavoriteGroup.visible(CurrentUser.user).name_or_id_matches(favgroup_name, CurrentUser.user)
|
||||
relation = relation.where.not(id: favgroup.select("unnest(post_ids)"))
|
||||
relation = relation.merge(favgroup_matches(favgroup_name).negate)
|
||||
end
|
||||
|
||||
q[:favgroup].to_a.each do |favgroup_name|
|
||||
favgroup = FavoriteGroup.visible(CurrentUser.user).name_or_id_matches(favgroup_name, CurrentUser.user)
|
||||
relation = relation.where(id: favgroup.select("unnest(post_ids)"))
|
||||
relation = relation.merge(favgroup_matches(favgroup_name))
|
||||
end
|
||||
|
||||
q[:upvoter].to_a.each do |username|
|
||||
@@ -858,7 +892,8 @@ class PostQueryBuilder
|
||||
q[:pool] << g2
|
||||
|
||||
when "ordpool"
|
||||
q[:ordpool] = g2
|
||||
q[:ordpool] ||= []
|
||||
q[:ordpool] << g2
|
||||
|
||||
when "-favgroup"
|
||||
q[:favgroup_neg] ||= []
|
||||
@@ -909,17 +944,20 @@ class PostQueryBuilder
|
||||
q[:rating] << g2
|
||||
|
||||
when "-locked"
|
||||
q[:locked_negated] = g2.downcase
|
||||
q[:locked_neg] ||= []
|
||||
q[:locked_neg] << g2
|
||||
|
||||
when "locked"
|
||||
q[:locked] = g2.downcase
|
||||
q[:locked] ||= []
|
||||
q[:locked] << g2
|
||||
|
||||
when "id"
|
||||
q[:id] ||= []
|
||||
q[:id] << g2
|
||||
|
||||
when "-id"
|
||||
q[:post_id_negated] = g2.to_i
|
||||
q[:id_neg] ||= []
|
||||
q[:id_neg] << g2
|
||||
|
||||
when "width"
|
||||
q[:width] ||= []
|
||||
@@ -950,10 +988,12 @@ class PostQueryBuilder
|
||||
q[:file_size] << g2
|
||||
|
||||
when "source"
|
||||
q[:source] = g2
|
||||
q[:source] ||= []
|
||||
q[:source] << g2
|
||||
|
||||
when "-source"
|
||||
q[:source_neg] = g2
|
||||
q[:source_neg] ||= []
|
||||
q[:source_neg] << g2
|
||||
|
||||
when "date"
|
||||
q[:date] ||= []
|
||||
@@ -980,7 +1020,12 @@ class PostQueryBuilder
|
||||
q[:parent_neg] << g2
|
||||
|
||||
when "child"
|
||||
q[:child] = g2.downcase
|
||||
q[:child] ||= []
|
||||
q[:child] << g2
|
||||
|
||||
when "-child"
|
||||
q[:child_neg] ||= []
|
||||
q[:child_neg] << g2
|
||||
|
||||
when "order"
|
||||
g2 = g2.downcase
|
||||
@@ -1004,7 +1049,12 @@ class PostQueryBuilder
|
||||
q[:status] << g2
|
||||
|
||||
when "embedded"
|
||||
q[:embedded] = g2.downcase
|
||||
q[:embedded] ||= []
|
||||
q[:embedded] << g2
|
||||
|
||||
when "-embedded"
|
||||
q[:embedded_neg] ||= []
|
||||
q[:embedded_neg] << g2
|
||||
|
||||
when "filetype"
|
||||
q[:filetype] ||= []
|
||||
|
||||
@@ -159,6 +159,7 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
||||
assert_tag_match([posts[1], posts[0]], "id:#{posts[0].id}...#{posts[2].id}")
|
||||
|
||||
assert_tag_match([], "id:#{posts[0].id} id:#{posts[2].id}")
|
||||
assert_tag_match([posts[0]], "-id:#{posts[1].id} -id:#{posts[2].id}")
|
||||
assert_tag_match([posts[1]], "id:>#{posts[0].id} id:<#{posts[2].id}")
|
||||
end
|
||||
|
||||
@@ -259,6 +260,11 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
||||
|
||||
assert_tag_match([child], "child:none")
|
||||
assert_tag_match([parent], "child:any")
|
||||
assert_tag_match([], "child:garbage")
|
||||
|
||||
assert_tag_match([parent], "-child:none")
|
||||
assert_tag_match([child], "-child:any")
|
||||
assert_tag_match([child, parent], "-child:garbage")
|
||||
end
|
||||
|
||||
should "return posts for the favgroup:<name> metatag" do
|
||||
@@ -311,6 +317,8 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
||||
assert_tag_match([posts[1]], "-approver:#{users[0].name}")
|
||||
assert_tag_match([posts[1], posts[0]], "approver:any")
|
||||
assert_tag_match([posts[2]], "approver:none")
|
||||
assert_tag_match([posts[2]], "approver:NONE")
|
||||
assert_tag_match([], "approver:does_not_exist")
|
||||
end
|
||||
|
||||
should "return posts for the commenter:<name> metatag" do
|
||||
@@ -400,6 +408,9 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
||||
assert_tag_match([post2, post1], "commentary:true")
|
||||
assert_tag_match([post4, post3], "commentary:false")
|
||||
|
||||
assert_tag_match([post2, post1], "commentary:TRUE")
|
||||
assert_tag_match([post4, post3], "commentary:FALSE")
|
||||
|
||||
assert_tag_match([post4, post3], "-commentary:true")
|
||||
assert_tag_match([post2, post1], "-commentary:false")
|
||||
|
||||
@@ -512,6 +523,21 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
||||
assert_tag_match([], "filetype:garbage")
|
||||
end
|
||||
|
||||
should "return posts for the embedded:<true|false> metatag" do
|
||||
p1 = create(:post, has_embedded_notes: true)
|
||||
p2 = create(:post, has_embedded_notes: false)
|
||||
|
||||
assert_tag_match([p1], "embedded:true")
|
||||
assert_tag_match([p2], "embedded:false")
|
||||
|
||||
assert_tag_match([p2], "-embedded:true")
|
||||
assert_tag_match([p1], "-embedded:false")
|
||||
|
||||
assert_tag_match([], "embedded:false embedded:true")
|
||||
assert_tag_match([], "embedded:garbage")
|
||||
assert_tag_match([p2, p1], "-embedded:garbage")
|
||||
end
|
||||
|
||||
should "return posts for the tagcount:<n> metatags" do
|
||||
post = create(:post, tag_string: "artist:wokada copyright:vocaloid char:hatsune_miku twintails")
|
||||
|
||||
@@ -542,6 +568,7 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
||||
assert_tag_match([post3, post1], "-source:abcde")
|
||||
|
||||
assert_tag_match([post3], "source:none")
|
||||
assert_tag_match([post3], "source:NONE")
|
||||
assert_tag_match([post2, post1], "-source:none")
|
||||
end
|
||||
|
||||
@@ -649,6 +676,13 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
||||
assert_tag_match(all - [rating_locked], "-locked:rating")
|
||||
assert_tag_match(all - [note_locked], "-locked:note")
|
||||
assert_tag_match(all - [status_locked], "-locked:status")
|
||||
|
||||
assert_tag_match([rating_locked], "locked:RATING")
|
||||
assert_tag_match([status_locked], "-locked:rating -locked:note")
|
||||
assert_tag_match([], "locked:rating locked:note")
|
||||
|
||||
assert_tag_match([], "locked:garbage")
|
||||
assert_tag_match(all, "-locked:garbage")
|
||||
end
|
||||
|
||||
should "return posts for a upvote:<user>, downvote:<user> metatag" do
|
||||
@@ -668,11 +702,13 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
||||
disapproval = create(:post_disapproval, user: CurrentUser.user, post: disapproved, reason: "disinterest")
|
||||
|
||||
assert_tag_match([disapproved], "disapproved:#{CurrentUser.name}")
|
||||
assert_tag_match([disapproved], "disapproved:#{CurrentUser.name.upcase}")
|
||||
assert_tag_match([disapproved], "disapproved:disinterest")
|
||||
assert_tag_match([], "disapproved:breaks_rules")
|
||||
assert_tag_match([disapproved], "disapproved:DISINTEREST")
|
||||
assert_tag_match([], "disapproved:breaks_rules")
|
||||
|
||||
assert_tag_match([pending], "-disapproved:#{CurrentUser.name}")
|
||||
assert_tag_match([pending], "-disapproved:disinterest")
|
||||
assert_tag_match([pending], "-disapproved:#{CurrentUser.name}")
|
||||
assert_tag_match([pending], "-disapproved:disinterest")
|
||||
assert_tag_match([disapproved, pending], "-disapproved:breaks_rules")
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user