search_index.rb 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. # Copyright (C) 2012-2025 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. wildcard_or_term = 'term'
  132. data[:value] = if key_tmp.match?(%r{^(created_by|updated_by|owner|customer|user)_id})
  133. 1
  134. end
  135. when 'current_user.id'
  136. raise "Use current_user.id in selector, but no current_user is set #{data.inspect}" if !current_user_id
  137. data[:value] = []
  138. wildcard_or_term = 'terms'
  139. if key_tmp == 'out_of_office_replacement_id'
  140. data[:value].push User.find(current_user_id).out_of_office_agent_of.pluck(:id)
  141. else
  142. data[:value].push current_user_id
  143. end
  144. when 'current_user.organization_id'
  145. raise "Use current_user.id in selector, but no current_user is set #{data.inspect}" if !current_user_id
  146. wildcard_or_term = 'term'
  147. user = User.find_by(id: current_user_id)
  148. data[:value] = user.organization_id
  149. end
  150. if data[:value].is_a?(Array)
  151. data[:value].each do |value|
  152. next if !value.is_a?(String) || value !~ %r{[A-z]}
  153. key_tmp += '.keyword'
  154. break
  155. end
  156. elsif data[:value].is_a?(String) && %r{[A-z]}.match?(data[:value])
  157. key_tmp += '.keyword'
  158. end
  159. end
  160. # use .keyword and wildcard search in cases where query contains non A-z chars
  161. value_is_string = Array.wrap(data[:value]).any? { |v| v.is_a?(String) && v.match?(%r{[A-z]}) }
  162. if ['contains', 'contains not', 'starts with one of', 'ends with one of'].include?(data[:operator]) && value_is_string
  163. wildcard_or_term = 'wildcard'
  164. if !key_tmp.ends_with?('.keyword')
  165. key_tmp += '.keyword'
  166. end
  167. if data[:value].is_a?(Array)
  168. or_condition = {
  169. bool: {
  170. should: [],
  171. }
  172. }
  173. data[:value].each do |value|
  174. t = {}
  175. t[wildcard_or_term] = {}
  176. t[wildcard_or_term][key_tmp] = if data[:operator] == 'starts with one of'
  177. "#{value}*"
  178. elsif data[:operator] == 'ends with one of'
  179. "*#{value}"
  180. else
  181. "*#{value}*"
  182. end
  183. or_condition[:bool][:should] << t
  184. end
  185. data[:value] = or_condition
  186. else
  187. data[:value] = "*#{data[:value]}*"
  188. end
  189. end
  190. if table != target_name
  191. key_tmp = "#{table}.#{key_tmp}"
  192. end
  193. # for pre condition not_set we want to check if values are defined for the object by exists
  194. if operators_is_isnot.include?(data[:operator]) && data[:value].nil?
  195. t['exists'] = {
  196. field: key_tmp,
  197. }
  198. case data[:operator]
  199. when 'is'
  200. query_must_not.push t
  201. when 'is not'
  202. query_must.push t
  203. end
  204. elsif data[:value].is_a?(Hash) && data[:value][:bool].present?
  205. query_must.push data[:value]
  206. # is/is not/contains/contains not
  207. elsif ['is', 'is not', 'contains', 'contains not', 'is any of', 'is none of'].include?(data[:operator])
  208. t[wildcard_or_term] = {}
  209. t[wildcard_or_term][key_tmp] = data[:value]
  210. case data[:operator]
  211. when 'is', 'contains', 'is any of'
  212. query_must.push t
  213. when 'is not', 'contains not', 'is none of'
  214. query_must_not.push t
  215. end
  216. elsif ['contains all', 'contains one', 'contains all not', 'contains one not'].include?(data[:operator])
  217. values = data[:value]
  218. if data[:value].is_a?(String)
  219. values = values.split(',').map(&:strip)
  220. end
  221. t[:query_string] = {}
  222. case data[:operator]
  223. when 'contains all'
  224. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" AND "')}\")"
  225. query_must.push t
  226. when 'contains one not'
  227. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" OR "')}\")"
  228. query_must_not.push t
  229. when 'contains one'
  230. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" OR "')}\")"
  231. query_must.push t
  232. when 'contains all not'
  233. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" AND "')}\")"
  234. query_must_not.push t
  235. end
  236. # within last/within next (relative)
  237. elsif ['within last (relative)', 'within next (relative)'].include?(data[:operator])
  238. range = relative_map[data[:range].to_sym]
  239. if range.blank?
  240. raise "Invalid relative_map for range '#{data[:range]}'."
  241. end
  242. t[:range] = {}
  243. t[:range][key_tmp] = {}
  244. if data[:operator] == 'within last (relative)'
  245. t[:range][key_tmp][:gte] = "now-#{data[:value]}#{range}"
  246. else
  247. t[:range][key_tmp][:lt] = "now+#{data[:value]}#{range}"
  248. end
  249. query_must.push t
  250. # before/after (relative)
  251. elsif ['before (relative)', 'after (relative)'].include?(data[:operator])
  252. range = relative_map[data[:range].to_sym]
  253. if range.blank?
  254. raise "Invalid relative_map for range '#{data[:range]}'."
  255. end
  256. t[:range] = {}
  257. t[:range][key_tmp] = {}
  258. if data[:operator] == 'before (relative)'
  259. t[:range][key_tmp][:lt] = "now-#{data[:value]}#{range}"
  260. else
  261. t[:range][key_tmp][:gt] = "now+#{data[:value]}#{range}"
  262. end
  263. query_must.push t
  264. # till/from (relative)
  265. elsif ['till (relative)', 'from (relative)'].include?(data[:operator])
  266. range = relative_map[data[:range].to_sym]
  267. if range.blank?
  268. raise "Invalid relative_map for range '#{data[:range]}'."
  269. end
  270. t[:range] = {}
  271. t[:range][key_tmp] = {}
  272. if data[:operator] == 'till (relative)'
  273. t[:range][key_tmp][:lt] = "now+#{data[:value]}#{range}"
  274. else
  275. t[:range][key_tmp][:gt] = "now-#{data[:value]}#{range}"
  276. end
  277. query_must.push t
  278. # before/after (absolute)
  279. elsif ['before (absolute)', 'after (absolute)'].include?(data[:operator])
  280. t[:range] = {}
  281. t[:range][key_tmp] = {}
  282. if data[:operator] == 'before (absolute)'
  283. t[:range][key_tmp][:lt] = (data[:value])
  284. else
  285. t[:range][key_tmp][:gt] = (data[:value])
  286. end
  287. query_must.push t
  288. elsif data[:operator] == 'today'
  289. t[:range] = {}
  290. t[:range][key_tmp] = {}
  291. t[:range][key_tmp][:gte] = "#{Time.zone.today}T00:00:00Z"
  292. t[:range][key_tmp][:lte] = "#{Time.zone.today}T23:59:59Z"
  293. query_must.push t
  294. else
  295. raise "unknown operator '#{data[:operator]}' for #{key}"
  296. end
  297. data = {
  298. bool: {},
  299. }
  300. if query_must.present?
  301. data[:bool][:must] = query_must
  302. end
  303. if query_must_not.present?
  304. data[:bool][:must_not] = query_must_not
  305. end
  306. data
  307. end
  308. end