first pass, mostly works

This commit is contained in:
byte 2019-02-17 14:17:35 -05:00 committed by Liam P. White
parent d1aa2e69f5
commit 2b7c1765a0
4 changed files with 270 additions and 326 deletions

View File

@ -6,6 +6,8 @@ module PostIndex
mappings dynamic: false, _all: { enabled: false } do
indexes :created_at, type: 'date'
indexes :updated_at, type: 'date'
indexes :commented_at, type: 'date'
indexes :noted_at, type: 'date'
indexes :id, type: 'integer'
indexes :up_score, type: 'integer'
indexes :down_score, type: 'integer'
@ -23,12 +25,17 @@ module PostIndex
indexes :file_size, type: 'integer'
indexes :pixiv_id, type: 'integer'
indexes :uploader_id, type: 'integer'
indexes :approver_id, type: 'integer'
indexes :parent_id, type: 'integer'
indexes :child_ids, type: 'integer'
indexes :pool_ids, type: 'integer'
indexes :set_ids, type: 'integer'
indexes :upvoter_ids, type: 'integer'
indexes :downvoter_ids, type: 'integer'
indexes :width, type: 'integer'
indexes :height, type: 'integer'
indexes :mpixels, type: 'float'
indexes :aspect_ratio, type: 'float'
indexes :tags, type: 'keyword'
@ -47,12 +54,14 @@ module PostIndex
indexes :rating_locked, type: 'boolean'
indexes :note_locked, type: 'boolean'
indexes :status_locked, type: 'boolean'
indexes :hide_anon, type: 'boolean'
indexes :hide_google, type: 'boolean'
indexes :flagged, type: 'boolean'
indexes :pending, type: 'boolean'
indexes :deleted, type: 'boolean'
indexes :has_description, type: 'boolean'
indexes :has_children, type: 'boolean'
indexes :description, type: 'text', analyzer: 'snowball'
end
@ -63,6 +72,8 @@ module PostIndex
{
created_at: created_at,
updated_at: updated_at,
commented_at: last_commented_at,
noted_at: last_noted_at,
id: id,
up_score: up_score,
down_score: down_score,
@ -80,13 +91,18 @@ module PostIndex
file_size: file_size,
pixiv_id: pixiv_id,
uploader_id: uploader_id,
approver_id: approver_id,
parent_id: parent_id,
# child_ids: child_ids,
# pool_ids: pool_ids,
# set_ids: set_ids,
# upvoter_ids: upvoter_ids,
# downvoter_ids: downvoter_ids,
width: image_width,
height: image_height,
aspect_ratio: image_width.to_f / [image_height, 1].max,
mpixels: (image_width.to_f * image_height / 1_000_000).round(2),
aspect_ratio: (image_width.to_f / [image_height, 1].max).round(2),
tags: tag_string.split(' '),
pools: pool_string.split(' '),
@ -94,7 +110,7 @@ module PostIndex
md5: md5,
rating: rating,
file_ext: file_ext,
source: source.presence,
source: source.downcase.presence,
faves: fav_string.split(' '),
# upvotes: upvotes,
# downvotes: downvotes,
@ -104,12 +120,14 @@ module PostIndex
rating_locked: is_rating_locked,
note_locked: is_note_locked,
status_locked: is_status_locked,
# hide_anon: hide_anon,
# hide_google: hide_google,
flagged: is_flagged,
pending: is_pending,
deleted: is_deleted,
# has_description: description.present?,
has_children: has_children,
# description: description.presence,
}

View File

@ -1,6 +1,10 @@
class PostQueryBuilder
attr_accessor :query_string, :read_only
SEARCHABLE_COUNT_METATAGS = [
:comment_count,
].freeze
def initialize(query_string, read_only: false)
@query_string = query_string
@read_only = read_only
@ -12,32 +16,31 @@ class PostQueryBuilder
case arr[0]
when :eq
if arr[1].is_a?(Time)
relation.where("#{field} between ? and ?", arr[1].beginning_of_day, arr[1].end_of_day)
relation.concat([
{ range: { field => { gte: arr[1].beginning_of_day } } },
{ range: { field => { lte: arr[1].end_of_day } } },
])
else
relation.where(["#{field} = ?", arr[1]])
relation.push({ term: { field => arr[1] } })
end
when :gt
relation.where(["#{field} > ?", arr[1]])
relation.push({ range: { field => { gt: arr[1] } } })
when :gte
relation.where(["#{field} >= ?", arr[1]])
relation.push({ range: { field => { gte: arr[1] } } })
when :lt
relation.where(["#{field} < ?", arr[1]])
relation.push({ range: { field => { lt: arr[1] } } })
when :lte
relation.where(["#{field} <= ?", arr[1]])
relation.push({ range: { field => { lte: arr[1] } } })
when :in
relation.where(["#{field} in (?)", arr[1]])
relation.push({ terms: { field => arr[1] } })
when :between
relation.where(["#{field} BETWEEN ? AND ?", arr[1], arr[2]])
else
relation
relation.concat([
{ range: { field => { gte: arr[1] } } },
{ range: { field => { lte: arr[2] } } },
])
end
relation
end
def escape_string_for_tsquery(array)
@ -47,42 +50,30 @@ class PostQueryBuilder
end
def add_tag_string_search_relation(tags, relation)
tag_query_sql = []
should = tags[:include].map { |x| { term: { tags: x } } }
must = tags[:related].map { |x| { term: { tags: x } } }
must_not = tags[:exclude].map { |x| { term: { tags: x } } }
if tags[:include].any?
tag_query_sql << "(" + escape_string_for_tsquery(tags[:include]).join(" | ") + ")"
end
if tags[:related].any?
tag_query_sql << "(" + escape_string_for_tsquery(tags[:related]).join(" & ") + ")"
end
if tags[:exclude].any?
tag_query_sql << "!(" + escape_string_for_tsquery(tags[:exclude]).join(" | ") + ")"
end
if tag_query_sql.any?
relation = relation.where("posts.tag_index @@ to_tsquery('danbooru', E?)", tag_query_sql.join(" & "))
end
relation
relation.push({ bool: {
should: should,
must: must,
must_not: must_not,
}})
end
def add_saved_search_relation(saved_searches, relation)
def saved_search_relation(saved_searches, should)
if SavedSearch.enabled?
saved_searches.each do |saved_search|
saved_searches.map do |saved_search|
if saved_search == "all"
post_ids = SavedSearch.post_ids_for(CurrentUser.id)
else
post_ids = SavedSearch.post_ids_for(CurrentUser.id, label: saved_search)
end
post_ids = [0] if post_ids.empty?
relation = relation.where("posts.id": post_ids)
post_ids = [] if post_ids.empty?
should.push({ terms: { id: post_ids } })
end
end
relation
end
def table_for_metatag(metatag)
@ -114,323 +105,256 @@ class PostQueryBuilder
true
end
def sql_like_to_elastic(query)
# First escape any existing wildcard characters
# in the term
query = query.gsub(/
(?<!\\) # not preceded by a backslash
(?:\\\\)* # zero or more escaped backslashes
(\*|\?) # single asterisk or question mark
/x, '\\\\\1')
# Then replace any unescaped SQL LIKE characters
# with a Kleene star
query = query.gsub(/
(?<!\\) # not preceded by a backslash
(?:\\\\)* # zero or more escaped backslashes
% # single percent sign
/x, '*')
# Collapse runs of wildcards for efficiency
query = query.gsub(/(?:\*)+\*/, '*')
{ wildcard: { source: query } }
end
def build
unless query_string.is_a?(Hash)
if query_string.is_a?(Hash)
q = query_string
else
q = Tag.parse_query(query_string)
end
relation = read_only ? PostReadOnly.all : Post.all
if q[:tag_count].to_i > Danbooru.config.tag_query_limit
raise ::Post::SearchError.new("You cannot search for more than #{Danbooru.config.tag_query_limit} tags at a time")
end
should = [] # These terms are ORed together
must = [] # These terms are ANDed together
must_not = [] # These terms are NOT ANDed together
order = []
if CurrentUser.safe_mode?
relation = relation.where("posts.rating = 's'")
must.push({ term: { 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)
relation = add_range_relation(q[:width], "posts.image_width", relation)
relation = add_range_relation(q[:height], "posts.image_height", relation)
relation = add_range_relation(q[:score], "posts.score", relation)
relation = add_range_relation(q[:fav_count], "posts.fav_count", relation)
relation = add_range_relation(q[:filesize], "posts.file_size", relation)
relation = add_range_relation(q[:date], "posts.created_at", relation)
relation = add_range_relation(q[:age], "posts.created_at", relation)
add_range_relation(q[:post_id], :id, must)
add_range_relation(q[:mpixels], :mpixels, must)
add_range_relation(q[:ratio], :aspect_ratio, must)
add_range_relation(q[:width], :width, must)
add_range_relation(q[:height], :height, must)
add_range_relation(q[:score], :score, must)
add_range_relation(q[:fav_count], :fav_count, must)
add_range_relation(q[:filesize], :file_size, must)
add_range_relation(q[:date], :created_at, must)
add_range_relation(q[:age], :created_at, must)
TagCategory.categories.each do |category|
relation = add_range_relation(q["#{category}_tag_count".to_sym], "posts.tag_count_#{category}", relation)
add_range_relation(q["#{category}_tag_count".to_sym], "tag_count_#{category}", must)
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)
add_range_relation(q[:post_tag_count], :tag_count, must)
SEARCHABLE_COUNT_METATAGS.each do |column|
add_range_relation(q[column], column, must)
end
if q[:md5]
relation = relation.where("posts.md5": q[:md5])
must.push({ term: { md5: [*q[:md5]][0] } })
end
if q[:status] == "pending"
relation = relation.where("posts.is_pending = TRUE")
must.push({ term: { pending: true } })
elsif q[:status] == "flagged"
relation = relation.where("posts.is_flagged = TRUE")
must.push({ term: { flagged: true } })
elsif q[:status] == "modqueue"
relation = relation.where("posts.is_pending = TRUE OR posts.is_flagged = TRUE")
should.concat([{ term: { pending: true } }, { term: { flagged: true } }])
elsif q[:status] == "deleted"
relation = relation.where("posts.is_deleted = TRUE")
must.push({ term: { deleted: true } })
elsif q[:status] == "active"
relation = relation.where("posts.is_pending = FALSE AND posts.is_deleted = FALSE AND posts.is_flagged = FALSE")
must.concat([
{ term: { pending: false } },
{ term: { deleted: false } },
{ term: { flagged: false } },
])
elsif q[:status] == "unmoderated"
relation = relation.merge(Post.pending_or_flagged.available_for_moderation)
fail ArgumentError, "unhandled case unmoderated"
# relation = relation.merge(Post.pending_or_flagged.available_for_moderation)
elsif q[:status] == "all" || q[:status] == "any"
# do nothing
elsif q[:status_neg] == "pending"
relation = relation.where("posts.is_pending = FALSE")
must.push({ term: { pending: false } })
elsif q[:status_neg] == "flagged"
relation = relation.where("posts.is_flagged = FALSE")
must.push({ term: { flagged: false } })
elsif q[:status_neg] == "modqueue"
relation = relation.where("posts.is_pending = FALSE AND posts.is_flagged = FALSE")
must.concat([
{ term: { pending: false } },
{ term: { flagged: false } },
])
elsif q[:status_neg] == "deleted"
relation = relation.where("posts.is_deleted = FALSE")
must.push({ term: { deleted: false } })
elsif q[:status_neg] == "active"
relation = relation.where("posts.is_pending = TRUE OR posts.is_deleted = TRUE OR posts.is_flagged = TRUE")
should.concat([
{ term: { pending: true } },
{ term: { deleted: true } },
{ term: { flagged: true} },
])
end
if hide_deleted_posts?(q)
relation = relation.where("posts.is_deleted = FALSE")
must.push({ term: { deleted: false } })
end
if q[:filetype]
relation = relation.where("posts.file_ext": q[:filetype])
must.push({ term: { file_ext: q[:filetype] } })
end
if q[:filetype_neg]
relation = relation.where.not("posts.file_ext": q[:filetype_neg])
must_not.push({ term: { file_ext: q[:filetype_neg] } })
end
# The SourcePattern SQL function replaces Pixiv sources with "pixiv/[suffix]", where
# [suffix] is everything past the second-to-last slash in the URL. It leaves non-Pixiv
# URLs unchanged. This is to ease database load for Pixiv source searches.
if q[:source]
if q[:source] == "none%"
relation = relation.where("posts.source = ''")
must_not.push({ exists: { field: :source } })
elsif q[:source] == "http%"
relation = relation.where("(lower(posts.source) like ?)", "http%")
elsif q[:source] =~ /^(?:https?:\/\/)?%\.?pixiv(?:\.net(?:\/img)?)?(?:%\/img\/|%\/|(?=%$))(.+)$/i
relation = relation.where("SourcePattern(lower(posts.source)) LIKE lower(?) ESCAPE E'\\\\'", "pixiv/" + $1)
must.push({ prefix: { source: "http" } })
else
relation = relation.where("SourcePattern(lower(posts.source)) LIKE SourcePattern(lower(?)) ESCAPE E'\\\\'", q[:source])
must.push(sql_like_to_elastic(q[:source]))
end
end
if q[:source_neg]
if q[:source_neg] == "none%"
relation = relation.where("posts.source != ''")
relation.push({ exists: { field: :source } })
elsif q[:source_neg] == "http%"
relation = relation.where("(lower(posts.source) not like ?)", "http%")
elsif q[:source_neg] =~ /^(?:https?:\/\/)?%\.?pixiv(?:\.net(?:\/img)?)?(?:%\/img\/|%\/|(?=%$))(.+)$/i
relation = relation.where("SourcePattern(lower(posts.source)) NOT LIKE lower(?) ESCAPE E'\\\\'", "pixiv/" + $1)
must_not.push({ prefix: { source: "http" } })
else
relation = relation.where("SourcePattern(lower(posts.source)) NOT LIKE SourcePattern(lower(?)) ESCAPE E'\\\\'", q[:source_neg])
must_not.push(sql_like_to_elastic(q[:source_neg]))
end
end
if q[:pool] == "none"
relation = relation.where("posts.pool_string = ''")
must_not.push({ exists: { field: :pools } })
elsif q[:pool] == "any"
relation = relation.where("posts.pool_string != ''")
must.push({ exists: { field: :pools } })
end
if q[:saved_searches]
relation = add_saved_search_relation(q[:saved_searches], relation)
saved_search_relation(q[:saved_searches], should)
end
if q[:uploader_id_neg]
relation = relation.where.not("posts.uploader_id": q[:uploader_id_neg])
must_not.push({ term: { uploader_id: q[:uploader_id_neg].to_i } })
end
if q[:uploader_id]
relation = relation.where("posts.uploader_id": q[:uploader_id])
must.push({ term: { uploader_id: q[:uploader_id].to_i } })
end
if q[:approver_id_neg]
relation = relation.where.not("posts.approver_id": q[:approver_id_neg])
must_not.push({ term: { approver_id: q[:approver_id_neg].to_i } })
end
if q[:approver_id]
if q[:approver_id] == "any"
relation = relation.where("posts.approver_id is not null")
must.push({ exists: { field: :approver_id } })
elsif q[:approver_id] == "none"
relation = relation.where("posts.approver_id is null")
must_not.push({ exists: { field: :approver_id } })
else
relation = relation.where("posts.approver_id": q[:approver_id])
end
end
if q[:disapproval]
q[:disapproval].each do |disapproval|
disapprovals = CurrentUser.user.post_disapprovals.select(:post_id)
if disapproval.in?(%w[none false])
relation = relation.where.not("posts.id": disapprovals)
elsif disapproval.in?(%w[any all true])
relation = relation.where("posts.id": disapprovals)
else
relation = relation.where("posts.id": disapprovals.where(reason: disapproval))
end
end
end
if q[:disapproval_neg]
q[:disapproval_neg].each do |disapproval|
disapprovals = CurrentUser.user.post_disapprovals.select(:post_id)
if disapproval.in?(%w[none false])
relation = relation.where("posts.id": disapprovals)
elsif disapproval.in?(%w[any all true])
relation = relation.where.not("posts.id": disapprovals)
else
relation = relation.where.not("posts.id": disapprovals.where(reason: disapproval))
end
end
end
if q[:flagger_ids_neg]
q[:flagger_ids_neg].each do |flagger_id|
if CurrentUser.can_view_flagger?(flagger_id)
post_ids = PostFlag.unscoped.search({:creator_id => flagger_id, :category => "normal"}).reorder("").select {|flag| flag.not_uploaded_by?(CurrentUser.id)}.map {|flag| flag.post_id}.uniq
if post_ids.any?
relation = relation.where.not("posts.id": post_ids)
end
end
end
end
if q[:flagger_ids]
q[:flagger_ids].each do |flagger_id|
if flagger_id == "any"
relation = relation.where('EXISTS (' + PostFlag.unscoped.search({:category => "normal"}).where('post_id = posts.id').reorder('').select('1').to_sql + ')')
elsif flagger_id == "none"
relation = relation.where('NOT EXISTS (' + PostFlag.unscoped.search({:category => "normal"}).where('post_id = posts.id').reorder('').select('1').to_sql + ')')
elsif CurrentUser.can_view_flagger?(flagger_id)
post_ids = PostFlag.unscoped.search({:creator_id => flagger_id, :category => "normal"}).reorder("").select {|flag| flag.not_uploaded_by?(CurrentUser.id)}.map {|flag| flag.post_id}.uniq
relation = relation.where("posts.id": post_ids)
end
end
end
if q[:appealer_ids_neg]
q[:appealer_ids_neg].each do |appealer_id|
relation = relation.where.not("posts.id": PostAppeal.unscoped.where(creator_id: appealer_id).select(:post_id).distinct)
end
end
if q[:appealer_ids]
q[:appealer_ids].each do |appealer_id|
if appealer_id == "any"
relation = relation.where('EXISTS (' + PostAppeal.unscoped.where('post_id = posts.id').select('1').to_sql + ')')
elsif appealer_id == "none"
relation = relation.where('NOT EXISTS (' + PostAppeal.unscoped.where('post_id = posts.id').select('1').to_sql + ')')
else
relation = relation.where("posts.id": PostAppeal.unscoped.where(creator_id: appealer_id).select(:post_id).distinct)
end
end
end
if q[:commenter_ids]
q[:commenter_ids].each do |commenter_id|
if commenter_id == "any"
relation = relation.where("posts.last_commented_at is not null")
elsif commenter_id == "none"
relation = relation.where("posts.last_commented_at is null")
else
relation = relation.where("posts.id": Comment.unscoped.where(creator_id: commenter_id).select(:post_id).distinct)
end
end
end
if q[:noter_ids]
q[:noter_ids].each do |noter_id|
if noter_id == "any"
relation = relation.where("posts.last_noted_at is not null")
elsif noter_id == "none"
relation = relation.where("posts.last_noted_at is null")
else
relation = relation.where("posts.id": Note.unscoped.where(creator_id: noter_id).select("post_id").distinct)
end
end
end
if q[:note_updater_ids]
q[:note_updater_ids].each do |note_updater_id|
relation = relation.where("posts.id": NoteVersion.unscoped.where(updater_id: note_updater_id).select("post_id").distinct)
end
end
if q[:artcomm_ids]
q[:artcomm_ids].each do |artcomm_id|
relation = relation.where("posts.id": ArtistCommentaryVersion.unscoped.where(updater_id: artcomm_id).select("post_id").distinct)
must.push({ term: { approver_id: q[:approver_id].to_i } })
end
end
if q[:post_id_negated]
relation = relation.where("posts.id <> ?", q[:post_id_negated])
must_not.push({ term: { id: q[:post_id_negated].to_i } })
end
if q[:parent] == "none"
relation = relation.where("posts.parent_id IS NULL")
must_not.push({ exists: { field: :parent_id } })
elsif q[:parent] == "any"
relation = relation.where("posts.parent_id IS NOT NULL")
must.push({ exists: { field: :parent_id } })
elsif q[:parent]
relation = relation.where("(posts.id = ? or posts.parent_id = ?)", q[:parent].to_i, q[:parent].to_i)
should.concat([
{ term: { id: q[:parent].to_i } },
{ term: { parent_id: q[:parent].to_i } },
])
end
if q[:parent_neg_ids]
neg_ids = q[:parent_neg_ids].map(&:to_i)
neg_ids.delete(0)
if neg_ids.present?
relation = relation.where("posts.id not in (?) and (posts.parent_id is null or posts.parent_id not in (?))", neg_ids, neg_ids)
# Negated version of the above
must_not.push({ bool: {
should: [
{ term: { id: q[:parent].to_i } },
{ term: { parent_id: q[:parent].to_i } },
],
}})
end
end
if q[:child] == "none"
relation = relation.where("posts.has_children = FALSE")
must.push({ term: { has_children: false } })
elsif q[:child] == "any"
relation = relation.where("posts.has_children = TRUE")
must.push({ term: { has_children: true } })
end
if q[:pixiv_id]
if q[:pixiv_id] == "any"
relation = relation.where("posts.pixiv_id IS NOT NULL")
must.push({ exists: { field: :pixiv_id } })
elsif q[:pixiv_id] == "none"
relation = relation.where("posts.pixiv_id IS NULL")
must_not.push({ exists: { field: :pixiv_id } })
else
relation = add_range_relation(q[:pixiv_id], "posts.pixiv_id", relation)
must.push({ term: { pixiv_id: q[:pixiv_id].to_i } })
end
end
if q[:rating] =~ /^q/
relation = relation.where("posts.rating = 'q'")
elsif q[:rating] =~ /^s/
relation = relation.where("posts.rating = 's'")
elsif q[:rating] =~ /^e/
relation = relation.where("posts.rating = 'e'")
if q[:rating] =~ /\Aq/
must.push({ term: { rating: "q" } })
elsif q[:rating] =~ /\As/
must.push({ term: { rating: "s" } })
elsif q[:rating] =~ /\Ae/
must.push({ term: { rating: "e" } })
end
if q[:rating_negated] =~ /^q/
relation = relation.where("posts.rating <> 'q'")
elsif q[:rating_negated] =~ /^s/
relation = relation.where("posts.rating <> 's'")
elsif q[:rating_negated] =~ /^e/
relation = relation.where("posts.rating <> 'e'")
if q[:rating_negated] =~ /\Aq/
must_not.push({ term: { rating: "q" } })
elsif q[:rating_negated] =~ /\As/
must_not.push({ term: { rating: "s" } })
elsif q[:rating_negated] =~ /\Ae/
must_not.push({ term: { rating: "e" } })
end
if q[:locked] == "rating"
relation = relation.where("posts.is_rating_locked = TRUE")
must.push({ term: { rating_locked: true } })
elsif q[:locked] == "note" || q[:locked] == "notes"
relation = relation.where("posts.is_note_locked = TRUE")
must.push({ term: { note_locked: true } })
elsif q[:locked] == "status"
relation = relation.where("posts.is_status_locked = TRUE")
must.push({ term: { status_locked: true } })
end
if q[:locked_negated] == "rating"
relation = relation.where("posts.is_rating_locked = FALSE")
must.push({ term: { rating_locked: false } })
elsif q[:locked_negated] == "note" || q[:locked_negated] == "notes"
relation = relation.where("posts.is_note_locked = FALSE")
must.push({ term: { note_locked: false } })
elsif q[:locked_negated] == "status"
relation = relation.where("posts.is_status_locked = FALSE")
must.push({ term: { status_locked: false } })
end
relation = add_tag_string_search_relation(q[:tags], relation)
add_tag_string_search_relation(q[:tags], must)
if q[:ordpool].present?
pool_id = q[:ordpool].to_i
pool_posts = Pool.joins("CROSS JOIN unnest(pools.post_ids) WITH ORDINALITY AS row(post_id, pool_index)").where(id: pool_id).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")
fail ArgumentError, "pool ordering unimplemented"
#pool_id = q[:ordpool].to_i
#pool_posts = Pool.joins("CROSS JOIN unnest(pools.post_ids) WITH ORDINALITY AS row(post_id, pool_index)").where(id: pool_id).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")
end
if q[:favgroups_neg].present?
@ -438,7 +362,7 @@ class PostQueryBuilder
favgroup_id = favgroup_rec.to_i
favgroup = FavoriteGroup.where("favorite_groups.id = ?", favgroup_id).first
if favgroup
relation = relation.where.not("posts.id": favgroup.post_id_array)
must_not.push({ terms: { id: favgroup.post_id_array } })
end
end
end
@ -448,154 +372,142 @@ class PostQueryBuilder
favgroup_id = favgroup_rec.to_i
favgroup = FavoriteGroup.where("favorite_groups.id = ?", favgroup_id).first
if favgroup
relation = relation.where("posts.id": favgroup.post_id_array)
must.push({ terms: { id: favgroup.post_id_array } })
end
end
end
if q[:upvote].present?
user_id = q[:upvote]
post_ids = PostVote.where(:user_id => user_id).where("score > 0").limit(400).pluck(:post_id)
relation = relation.where("posts.id": post_ids)
must.push({ term: { upvoter_ids: q[:upvote].to_i } })
end
if q[:downvote].present?
user_id = q[:downvote]
post_ids = PostVote.where(:user_id => user_id).where("score < 0").limit(400).pluck(:post_id)
relation = relation.where("posts.id": post_ids)
must.push({ term: { downvoter_ids: q[:downvote].to_i } })
end
if q[:ordfav].present?
user_id = q[:ordfav].to_i
relation = relation.joins("INNER JOIN favorites ON favorites.post_id = posts.id")
relation = relation.where("favorites.user_id % 100 = ? and favorites.user_id = ?", user_id % 100, user_id).order("favorites.id DESC")
end
# HACK: if we're using a date: or age: metatag, default to ordering by
# created_at instead of id so that the query will use the created_at index.
if q[:date].present? || q[:age].present?
case q[:order]
when "id", "id_asc"
q[:order] = "created_at_asc"
when "id_desc", nil
q[:order] = "created_at_desc"
end
fail ArgumentError, "favorite ordering unimplemented"
#user_id = q[:ordfav].to_i
#relation = relation.joins("INNER JOIN favorites ON favorites.post_id = posts.id")
#relation = relation.where("favorites.user_id % 100 = ? and favorites.user_id = ?", user_id % 100, user_id).order("favorites.id DESC")
end
if q[:order] == "rank"
relation = relation.where("posts.score > 0 and posts.created_at >= ?", 2.days.ago)
elsif q[:order] == "landscape" || q[:order] == "portrait"
relation = relation.where("posts.image_width IS NOT NULL and posts.image_height IS NOT NULL")
must.push({ range: { score: { gt: 0 } } })
must.push({ range: { created_at: { gte: 2.days.ago } } })
elsif q[:order] == "landscape" || q[:order] == "portrait" ||
q[:order] == "mpixels" || q[:order] == "mpixels_desc"
must.push({ exists: { field: :width } })
must.push({ exists: { field: :height } })
end
case q[:order]
when "id", "id_asc"
relation = relation.order("posts.id ASC")
order.push({ id: :asc })
when "id_desc"
relation = relation.order("posts.id DESC")
order.push({ id: :desc })
when "score", "score_desc"
relation = relation.order("posts.score DESC, posts.id DESC")
order.concat([{ score: :desc }, { id: :desc }])
when "score_asc"
relation = relation.order("posts.score ASC, posts.id ASC")
order.concat([{ score: :asc }, { id: :asc }])
when "favcount"
relation = relation.order("posts.fav_count DESC, posts.id DESC")
order.concat([{ fav_count: :desc }, { id: :desc }])
when "favcount_asc"
relation = relation.order("posts.fav_count ASC, posts.id ASC")
order.concat([{ fav_count: :asc }, { id: :asc }])
when "created_at", "created_at_desc"
relation = relation.order("posts.created_at DESC")
order.push({ created_at: :desc })
when "created_at_asc"
relation = relation.order("posts.created_at ASC")
order.push({ created_at: :asc })
when "change", "change_desc"
relation = relation.order("posts.updated_at DESC, posts.id DESC")
order.concat([{ updated_at: :desc }, { id: :desc }])
when "change_asc"
relation = relation.order("posts.updated_at ASC, posts.id ASC")
order.concat([{ updated_at: :asc }, { id: :asc }])
when "comment", "comm"
relation = relation.order("posts.last_commented_at DESC NULLS LAST, posts.id DESC")
order.push({ commented_at: { order: :desc, missing: :_last } })
order.push({ id: :desc })
when "comment_asc", "comm_asc"
relation = relation.order("posts.last_commented_at ASC NULLS LAST, posts.id ASC")
when "comment_bumped"
relation = relation.order("posts.last_comment_bumped_at DESC NULLS LAST")
when "comment_bumped_asc"
relation = relation.order("posts.last_comment_bumped_at ASC NULLS FIRST")
order.push({ commented_at: { order: :asc, missing: :_last } })
order.push({ id: :asc })
when "note"
relation = relation.order("posts.last_noted_at DESC NULLS LAST")
order.push({ noted_at: { order: :desc, missing: :_last } })
when "note_asc"
relation = relation.order("posts.last_noted_at ASC NULLS FIRST")
when "artcomm"
relation = relation.joins("INNER JOIN artist_commentaries ON artist_commentaries.post_id = posts.id")
relation = relation.order("artist_commentaries.updated_at DESC")
when "artcomm_asc"
relation = relation.joins("INNER JOIN artist_commentaries ON artist_commentaries.post_id = posts.id")
relation = relation.order("artist_commentaries.updated_at ASC")
order.push({ noted_at: { order: :asc, missing: :_first } })
when "mpixels", "mpixels_desc"
relation = relation.where(Arel.sql("posts.image_width is not null and posts.image_height is not null"))
# Use "w*h/1000000", even though "w*h" would give the same result, so this can use
# the posts_mpixels index.
relation = relation.order(Arel.sql("posts.image_width * posts.image_height / 1000000.0 DESC"))
order.push({ mpixels: :desc })
when "mpixels_asc"
relation = relation.where("posts.image_width is not null and posts.image_height is not null")
relation = relation.order(Arel.sql("posts.image_width * posts.image_height / 1000000.0 ASC"))
order.push({ mpixels: :asc })
when "portrait"
relation = relation.order(Arel.sql("1.0 * posts.image_width / GREATEST(1, posts.image_height) ASC"))
order.push({ aspect_ratio: :asc })
when "landscape"
relation = relation.order(Arel.sql("1.0 * posts.image_width / GREATEST(1, posts.image_height) DESC"))
order.push({ aspect_ratio: :desc })
when "filesize", "filesize_desc"
relation = relation.order("posts.file_size DESC")
order.push({ file_size: :desc })
when "filesize_asc"
relation = relation.order("posts.file_size ASC")
order.push({ file_size: :asc })
when /\A(?<column>#{Tag::COUNT_METATAGS.join("|")})(_(?<direction>asc|desc))?\z/i
column = $~[:column]
direction = $~[:direction] || "desc"
relation = relation.order(column => direction, :id => direction)
when /\A(?<column>#{SEARCHABLE_COUNT_METATAGS.join("|")})(_(?<direction>asc|desc))?\z/i
column = Regexp.last_match[:column]
direction = Regexp.last_match[:direction] || "desc"
order.concat([{ column => direction }, { id: direction }])
when "tagcount", "tagcount_desc"
relation = relation.order("posts.tag_count DESC")
order.push({ tag_count: :desc })
when "tagcount_asc"
relation = relation.order("posts.tag_count ASC")
order.push({ tag_count: :asc })
when /(#{TagCategory.short_name_regex})tags(?:\Z|_desc)/
relation = relation.order("posts.tag_count_#{TagCategory.short_name_mapping[$1]} DESC")
order.push({ "tag_count_#{TagCategory.short_name_mapping[$1]}" => :desc })
when /(#{TagCategory.short_name_regex})tags_asc/
relation = relation.order("posts.tag_count_#{TagCategory.short_name_mapping[$1]} ASC")
order.push({ "tag_count_#{TagCategory.short_name_mapping[$1]}" => :asc })
when "rank"
relation = relation.order(Arel.sql("log(3, posts.score) + (extract(epoch from posts.created_at) - extract(epoch from timestamp '2005-05-24')) / 35000 DESC"))
must.push({ function_score: {
query: { match_all: {} },
script_score: {
script: {
params: { log3: Math.log(3), date2005_05_24: 1116936000 },
source: "Math.log(doc['score'].value) / params.log3 + (doc['created_at'].date.millis / 1000 - params.date2005_05_24) / 35000",
},
},
}})
when "custom"
if q[:post_id].present? && q[:post_id][0] == :in
relation = relation.find_ordered(q[:post_id][1])
end
order.push({ _score: :desc })
else
relation = relation.order("posts.id DESC")
order.push({ id: :desc })
end
relation
if must.empty?
must.push({ match_all: {} })
end
search_body = {
query: { bool: { should: should, must: must, must_not: must_not } },
sort: order,
_source: false,
}
Post.__elasticsearch__.search(search_body).records
end
end

View File

@ -130,21 +130,20 @@ module PostSets
end
def posts
if tag_array.any? {|x| x =~ /^-?source:.*\*.*pixiv/} && !CurrentUser.user.is_builder?
raise SearchError.new("Your search took too long to execute and was canceled")
end
#if tag_array.any? {|x| x =~ /^-?source:.*\*.*pixiv/} && !CurrentUser.user.is_builder?
# raise SearchError.new("Your search took too long to execute and was canceled")
#end
@posts ||= begin
@post_count = get_post_count()
if is_random?
temp = get_random_posts()
temp = get_random_posts
elsif raw
temp = ::Post.raw_tag_match(tag_string).order("posts.id DESC").where("true /* PostSets::Post#posts:1 */").paginate(page, :count => post_count, :limit => per_page)
else
temp = ::Post.tag_match(tag_string, read_only).where("true /* PostSets::Post#posts:2 */").paginate(page, :count => post_count, :limit => per_page)
end
temp.each # hack to force rails to eager load
@post_count = temp.total_count
temp
end
end

View File

@ -885,6 +885,7 @@ class Post < ApplicationRecord
end
end
end
end
def apply_pre_metatags
@ -966,6 +967,7 @@ class Post < ApplicationRecord
self.fav_count = array.size
update_column(:fav_string, fav_string)
update_column(:fav_count, fav_count)
update_index
end
def favorited_by?(user_id = CurrentUser.id)
@ -988,15 +990,21 @@ class Post < ApplicationRecord
def add_favorite!(user)
Favorite.add(post: self, user: user)
reload
update_index
rescue PostVote::Error
end
def delete_user_from_fav_string(user_id)
update_column(:fav_string, fav_string.gsub(/(?:\A| )fav:#{user_id}(?:\Z| )/, " ").strip)
reload
update_index
end
def remove_favorite!(user)
Favorite.remove(post: self, user: user)
reload
update_index
rescue PostVote::Error
end
@ -1105,6 +1113,8 @@ class Post < ApplicationRecord
set_pool_category_pseudo_tags
update_column(:pool_string, pool_string) unless new_record?
pool.add!(self)
reload
update_index
end
end
@ -1117,6 +1127,8 @@ class Post < ApplicationRecord
set_pool_category_pseudo_tags
update_column(:pool_string, pool_string) unless new_record?
pool.remove!(self)
reload
update_index
end
end
@ -1161,6 +1173,7 @@ class Post < ApplicationRecord
votes.create!(user: voter, vote: vote)
reload # PostVote.create modifies our score. Reload to get the new score.
update_index
end
Cache.delete(ckey)
end
@ -1176,6 +1189,7 @@ class Post < ApplicationRecord
else
votes.where(user: voter).destroy_all
reload
update_index
end
end
Cache.delete(ckey)
@ -1248,6 +1262,7 @@ class Post < ApplicationRecord
if post.changes_saved?
args = Hash[TagCategory.categories.map {|x| ["tag_count_#{x}", post.send("tag_count_#{x}")]}].update(:tag_count => post.tag_count)
post.update_columns(args)
post.update_index
end
end