search_index.rb 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class Selector::SearchIndex < Selector::Base
  3. def get
  4. result = {
  5. size: options[:limit] || SearchIndexBackend::DEFAULT_QUERY_OPTIONS[:limit],
  6. }
  7. query = run(selector, 0)
  8. if query.present?
  9. result[:query] = query
  10. end
  11. result = query_aggs_range(result)
  12. query_sort(result)
  13. end
  14. def query_sort(query)
  15. if options[:aggs_interval].present? && options[:aggs_interval][:field].present? && options[:aggs_interval][:interval].blank?
  16. query_sort_by_aggs_interval(query)
  17. else
  18. query_sort_by_index(query)
  19. end
  20. query
  21. end
  22. def query_sort_by_index(query)
  23. query[:sort] = SearchIndexBackend.search_by_index_sort(index: target_class.to_s, sort_by: options[:sort_by], order_by: options[:order_by])
  24. query
  25. end
  26. def query_sort_by_aggs_interval(query)
  27. query[:sort] = [
  28. {
  29. options[:aggs_interval][:field] => {
  30. order: 'desc',
  31. }
  32. },
  33. '_score'
  34. ]
  35. query
  36. end
  37. def query_aggs_range(query)
  38. return query if options[:aggs_interval].blank?
  39. query = query_aggs_interval(query)
  40. query[:query] = {
  41. bool: {
  42. must: [
  43. {
  44. range: {
  45. options[:aggs_interval][:field] => {
  46. from: options[:aggs_interval][:from],
  47. to: options[:aggs_interval][:to],
  48. },
  49. },
  50. },
  51. query[:query],
  52. ],
  53. },
  54. }
  55. query
  56. end
  57. def query_aggs_interval(query)
  58. return query if options[:aggs_interval][:interval].blank?
  59. query[:size] = 0
  60. query[:aggs] = {
  61. time_buckets: {
  62. date_histogram: {
  63. field: options[:aggs_interval][:field],
  64. calendar_interval: options[:aggs_interval][:interval],
  65. }
  66. }
  67. }
  68. query_aggs_interval_timezone(query)
  69. end
  70. def query_aggs_interval_timezone(query)
  71. return query if options[:aggs_interval][:timezone].blank?
  72. query[:aggs][:time_buckets][:date_histogram][:time_zone] = options[:aggs_interval][:timezone]
  73. query
  74. end
  75. def run(block, level)
  76. if block.key?(:conditions)
  77. block_query = block[:conditions].map do |sub_block|
  78. run(sub_block, level + 1)
  79. end
  80. block_query = block_query.compact
  81. return if block_query.blank?
  82. operator = :must
  83. case block[:operator]
  84. when 'NOT'
  85. operator = :must_not
  86. when 'OR'
  87. operator = :should
  88. end
  89. {
  90. bool: {
  91. operator => block_query
  92. }
  93. }
  94. else
  95. condition_query(block)
  96. end
  97. end
  98. def condition_query(block_condition)
  99. query_must = []
  100. query_must_not = []
  101. current_user = options[:current_user]
  102. current_user_id = UserInfo.current_user_id
  103. if current_user
  104. current_user_id = current_user.id
  105. end
  106. relative_map = {
  107. day: 'd',
  108. year: 'y',
  109. month: 'M',
  110. week: 'w',
  111. hour: 'h',
  112. minute: 'm',
  113. }
  114. operators_is_isnot = ['is', 'is not']
  115. data = block_condition.clone
  116. key = data[:name]
  117. table, key_tmp = key.split('.')
  118. if key_tmp.blank?
  119. key_tmp = table
  120. table = target_name
  121. end
  122. wildcard_or_term = 'term'
  123. if data[:value].is_a?(Array)
  124. wildcard_or_term = 'terms'
  125. end
  126. t = {}
  127. # use .keyword in case of compare exact values
  128. if ['is', 'is not', 'is any of', 'is none of', 'starts with one of', 'ends with one of'].include?(data[:operator])
  129. case data[:pre_condition]
  130. when 'not_set'
  131. data[:value] = if key_tmp.match?(%r{^(created_by|updated_by|owner|customer|user)_id})
  132. 1
  133. end
  134. when 'current_user.id'
  135. raise "Use current_user.id in selector, but no current_user is set #{data.inspect}" if !current_user_id
  136. data[:value] = []
  137. wildcard_or_term = 'terms'
  138. if key_tmp == 'out_of_office_replacement_id'
  139. data[:value].push User.find(current_user_id).out_of_office_agent_of.pluck(:id)
  140. else
  141. data[:value].push current_user_id
  142. end
  143. when 'current_user.organization_id'
  144. raise "Use current_user.id in selector, but no current_user is set #{data.inspect}" if !current_user_id
  145. user = User.find_by(id: current_user_id)
  146. data[:value] = user.organization_id
  147. end
  148. if data[:value].is_a?(Array)
  149. data[:value].each do |value|
  150. next if !value.is_a?(String) || value !~ %r{[A-z]}
  151. key_tmp += '.keyword'
  152. break
  153. end
  154. elsif data[:value].is_a?(String) && %r{[A-z]}.match?(data[:value])
  155. key_tmp += '.keyword'
  156. end
  157. end
  158. # use .keyword and wildcard search in cases where query contains non A-z chars
  159. value_is_string = Array.wrap(data[:value]).any? { |v| v.is_a?(String) && v.match?(%r{[A-z]}) }
  160. if ['contains', 'contains not', 'starts with one of', 'ends with one of'].include?(data[:operator]) && value_is_string
  161. wildcard_or_term = 'wildcard'
  162. if !key_tmp.ends_with?('.keyword')
  163. key_tmp += '.keyword'
  164. end
  165. if data[:value].is_a?(Array)
  166. or_condition = {
  167. bool: {
  168. should: [],
  169. }
  170. }
  171. data[:value].each do |value|
  172. t = {}
  173. t[wildcard_or_term] = {}
  174. t[wildcard_or_term][key_tmp] = if data[:operator] == 'starts with one of'
  175. "#{value}*"
  176. elsif data[:operator] == 'ends with one of'
  177. "*#{value}"
  178. else
  179. "*#{value}*"
  180. end
  181. or_condition[:bool][:should] << t
  182. end
  183. data[:value] = or_condition
  184. else
  185. data[:value] = "*#{data[:value]}*"
  186. end
  187. end
  188. if table != target_name
  189. key_tmp = "#{table}.#{key_tmp}"
  190. end
  191. # for pre condition not_set we want to check if values are defined for the object by exists
  192. if data[:pre_condition] == 'not_set' && operators_is_isnot.include?(data[:operator]) && data[:value].nil?
  193. t['exists'] = {
  194. field: key_tmp,
  195. }
  196. case data[:operator]
  197. when 'is'
  198. query_must_not.push t
  199. when 'is not'
  200. query_must.push t
  201. end
  202. elsif data[:value].is_a?(Hash) && data[:value][:bool].present?
  203. query_must.push data[:value]
  204. # is/is not/contains/contains not
  205. elsif ['is', 'is not', 'contains', 'contains not', 'is any of', 'is none of'].include?(data[:operator])
  206. t[wildcard_or_term] = {}
  207. t[wildcard_or_term][key_tmp] = data[:value]
  208. case data[:operator]
  209. when 'is', 'contains', 'is any of'
  210. query_must.push t
  211. when 'is not', 'contains not', 'is none of'
  212. query_must_not.push t
  213. end
  214. elsif ['contains all', 'contains one', 'contains all not', 'contains one not'].include?(data[:operator])
  215. values = data[:value]
  216. if data[:value].is_a?(String)
  217. values = values.split(',').map(&:strip)
  218. end
  219. t[:query_string] = {}
  220. case data[:operator]
  221. when 'contains all'
  222. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" AND "')}\")"
  223. query_must.push t
  224. when 'contains one not'
  225. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" OR "')}\")"
  226. query_must_not.push t
  227. when 'contains one'
  228. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" OR "')}\")"
  229. query_must.push t
  230. when 'contains all not'
  231. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" AND "')}\")"
  232. query_must_not.push t
  233. end
  234. # within last/within next (relative)
  235. elsif ['within last (relative)', 'within next (relative)'].include?(data[:operator])
  236. range = relative_map[data[:range].to_sym]
  237. if range.blank?
  238. raise "Invalid relative_map for range '#{data[:range]}'."
  239. end
  240. t[:range] = {}
  241. t[:range][key_tmp] = {}
  242. if data[:operator] == 'within last (relative)'
  243. t[:range][key_tmp][:gte] = "now-#{data[:value]}#{range}"
  244. else
  245. t[:range][key_tmp][:lt] = "now+#{data[:value]}#{range}"
  246. end
  247. query_must.push t
  248. # before/after (relative)
  249. elsif ['before (relative)', 'after (relative)'].include?(data[:operator])
  250. range = relative_map[data[:range].to_sym]
  251. if range.blank?
  252. raise "Invalid relative_map for range '#{data[:range]}'."
  253. end
  254. t[:range] = {}
  255. t[:range][key_tmp] = {}
  256. if data[:operator] == 'before (relative)'
  257. t[:range][key_tmp][:lt] = "now-#{data[:value]}#{range}"
  258. else
  259. t[:range][key_tmp][:gt] = "now+#{data[:value]}#{range}"
  260. end
  261. query_must.push t
  262. # till/from (relative)
  263. elsif ['till (relative)', 'from (relative)'].include?(data[:operator])
  264. range = relative_map[data[:range].to_sym]
  265. if range.blank?
  266. raise "Invalid relative_map for range '#{data[:range]}'."
  267. end
  268. t[:range] = {}
  269. t[:range][key_tmp] = {}
  270. if data[:operator] == 'till (relative)'
  271. t[:range][key_tmp][:lt] = "now+#{data[:value]}#{range}"
  272. else
  273. t[:range][key_tmp][:gt] = "now-#{data[:value]}#{range}"
  274. end
  275. query_must.push t
  276. # before/after (absolute)
  277. elsif ['before (absolute)', 'after (absolute)'].include?(data[:operator])
  278. t[:range] = {}
  279. t[:range][key_tmp] = {}
  280. if data[:operator] == 'before (absolute)'
  281. t[:range][key_tmp][:lt] = (data[:value])
  282. else
  283. t[:range][key_tmp][:gt] = (data[:value])
  284. end
  285. query_must.push t
  286. elsif data[:operator] == 'today'
  287. t[:range] = {}
  288. t[:range][key_tmp] = {}
  289. t[:range][key_tmp][:gte] = "#{Time.zone.today}T00:00:00Z"
  290. t[:range][key_tmp][:lte] = "#{Time.zone.today}T23:59:59Z"
  291. query_must.push t
  292. else
  293. raise "unknown operator '#{data[:operator]}' for #{key}"
  294. end
  295. data = {
  296. bool: {},
  297. }
  298. if query_must.present?
  299. data[:bool][:must] = query_must
  300. end
  301. if query_must_not.present?
  302. data[:bool][:must_not] = query_must_not
  303. end
  304. data
  305. end
  306. end