forked from e621ng/e621ng
160 lines
4.0 KiB
Ruby
160 lines
4.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class ElasticQueryBuilder
|
|
attr_accessor :q, :must, :must_not, :should, :order
|
|
|
|
def initialize(query)
|
|
@q = query
|
|
@must = [] # These terms are ANDed together
|
|
@must_not = [] # These terms are NOT ANDed together
|
|
@should = [] # These terms are ORed together
|
|
@order = []
|
|
@function_score = nil
|
|
build
|
|
end
|
|
|
|
def search
|
|
if must.empty?
|
|
must.push({ match_all: {} })
|
|
end
|
|
|
|
query = {
|
|
bool: {
|
|
must: must,
|
|
must_not: must_not,
|
|
should: should,
|
|
},
|
|
}
|
|
|
|
query[:bool][:minimum_should_match] = 1 if should.any?
|
|
|
|
if @function_score.present?
|
|
@function_score[:query] = query
|
|
query = { function_score: @function_score }
|
|
end
|
|
|
|
search_body = {
|
|
query: query,
|
|
sort: order,
|
|
_source: false,
|
|
timeout: "#{CurrentUser.user.try(:statement_timeout) || 3_000}ms",
|
|
}
|
|
|
|
model_class.document_store.search(search_body)
|
|
end
|
|
|
|
def match_any(*args)
|
|
# Explicitly set minimum should match, even though it may not be required in this context.
|
|
{ bool: { minimum_should_match: 1, should: args } }
|
|
end
|
|
|
|
def match_none(*args)
|
|
{ bool: { must_not: args } }
|
|
end
|
|
|
|
def range_relation(arr, field)
|
|
return if arr.nil?
|
|
return if arr.size < 2
|
|
return if arr[1].nil?
|
|
|
|
case arr[0]
|
|
when :eq
|
|
if arr[1].is_a?(Time)
|
|
{ range: { field => { gte: arr[1].beginning_of_day, lte: arr[1].end_of_day } } }
|
|
else
|
|
{ term: { field => arr[1] } }
|
|
end
|
|
when :gt
|
|
{ range: { field => { gt: arr[1] } } }
|
|
when :gte
|
|
{ range: { field => { gte: arr[1] } } }
|
|
when :lt
|
|
{ range: { field => { lt: arr[1] } } }
|
|
when :lte
|
|
{ range: { field => { lte: arr[1] } } }
|
|
when :in
|
|
{ terms: { field => arr[1] } }
|
|
when :between
|
|
{ range: { field => { gte: arr[1], lte: arr[2] } } }
|
|
end
|
|
end
|
|
|
|
def add_array_range_relation(key, index_field)
|
|
if q[key]
|
|
must.concat(q[key].map { |x| range_relation(x, index_field) })
|
|
end
|
|
|
|
if q[:"#{key}_must_not"]
|
|
must_not.concat(q[:"#{key}_must_not"].map { |x| range_relation(x, index_field) })
|
|
end
|
|
|
|
if q[:"#{key}_should"]
|
|
should.concat(q[:"#{key}_should"].map { |x| range_relation(x, index_field) })
|
|
end
|
|
end
|
|
|
|
def add_array_relation(key, index_field, any_none_key: nil, action: :term)
|
|
if q[key]
|
|
must.concat(q[key].map { |x| { action => { index_field => x } } })
|
|
end
|
|
|
|
if q[:"#{key}_must_not"]
|
|
must_not.concat(q[:"#{key}_must_not"].map { |x| { action => { index_field => x } } })
|
|
end
|
|
|
|
if q[:"#{key}_should"]
|
|
should.concat(q[:"#{key}_should"].map { |x| { action => { index_field => x } } })
|
|
end
|
|
|
|
if q[any_none_key] == "any"
|
|
must.push({ exists: { field: index_field } })
|
|
elsif q[any_none_key] == "none"
|
|
must_not.push({ exists: { field: index_field } })
|
|
end
|
|
|
|
if q[:"#{any_none_key}_should"] == "any"
|
|
should.push({ exists: { field: index_field } })
|
|
elsif q[:"#{any_none_key}_should"] == "none"
|
|
should.push(match_none({ exists: { field: index_field } }))
|
|
end
|
|
end
|
|
|
|
def add_boolean_match(key, index_field)
|
|
return if q[key].blank?
|
|
if q[key].to_s.truthy?
|
|
must.push({ term: { index_field => true } })
|
|
elsif q[key].to_s.falsy?
|
|
must.push({ term: { index_field => false } })
|
|
else
|
|
raise ArgumentError, "value must be truthy or falsy"
|
|
end
|
|
end
|
|
|
|
def add_range_relation(key, index_field, type: :integer)
|
|
if q[key].present?
|
|
must.push(range_relation(ParseValue.range(q[key], type), index_field))
|
|
end
|
|
end
|
|
|
|
def add_text_match(key, index_field)
|
|
value = q[key]
|
|
return if value.blank?
|
|
must.push(match: { index_field => value })
|
|
end
|
|
|
|
def apply_basic_order(key: :order)
|
|
case q[key]
|
|
when "id_asc"
|
|
order.push({ id: { order: "asc" } })
|
|
when "id_desc"
|
|
order.push({ id: { order: "desc" } })
|
|
else
|
|
apply_default_order
|
|
end
|
|
end
|
|
|
|
def apply_default_order
|
|
order.push({ id: { order: "desc" } })
|
|
end
|
|
end
|