chewy 0.8.4 → 7.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (340) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.github/PULL_REQUEST_TEMPLATE.md +16 -0
  6. data/.github/workflows/ruby.yml +74 -0
  7. data/.gitignore +1 -0
  8. data/.rubocop.yml +61 -0
  9. data/.rubocop_todo.yml +132 -0
  10. data/.yardopts +5 -0
  11. data/CHANGELOG.md +554 -245
  12. data/CODE_OF_CONDUCT.md +14 -0
  13. data/CONTRIBUTING.md +63 -0
  14. data/Gemfile +14 -11
  15. data/Guardfile +8 -6
  16. data/LICENSE.txt +1 -1
  17. data/README.md +748 -623
  18. data/Rakefile +11 -1
  19. data/chewy.gemspec +15 -19
  20. data/gemfiles/rails.5.2.activerecord.gemfile +11 -0
  21. data/gemfiles/rails.6.0.activerecord.gemfile +11 -0
  22. data/gemfiles/rails.6.1.activerecord.gemfile +13 -0
  23. data/gemfiles/rails.7.0.activerecord.gemfile +13 -0
  24. data/lib/chewy/config.rb +64 -50
  25. data/lib/chewy/errors.rb +10 -16
  26. data/lib/chewy/fields/base.rb +122 -32
  27. data/lib/chewy/fields/root.rb +48 -23
  28. data/lib/chewy/index/actions.rb +140 -54
  29. data/lib/chewy/index/adapter/active_record.rb +112 -0
  30. data/lib/chewy/{type → index}/adapter/base.rb +31 -12
  31. data/lib/chewy/index/adapter/object.rb +249 -0
  32. data/lib/chewy/index/adapter/orm.rb +194 -0
  33. data/lib/chewy/index/aliases.rb +14 -4
  34. data/lib/chewy/index/crutch.rb +40 -0
  35. data/lib/chewy/index/import/bulk_builder.rb +311 -0
  36. data/lib/chewy/index/import/bulk_request.rb +77 -0
  37. data/lib/chewy/index/import/journal_builder.rb +44 -0
  38. data/lib/chewy/index/import/routine.rb +139 -0
  39. data/lib/chewy/index/import.rb +243 -0
  40. data/lib/chewy/{type → index}/mapping.rb +79 -68
  41. data/lib/chewy/index/observe/active_record_methods.rb +87 -0
  42. data/lib/chewy/index/observe/callback.rb +34 -0
  43. data/lib/chewy/index/observe.rb +17 -0
  44. data/lib/chewy/index/settings.rb +10 -5
  45. data/lib/chewy/index/specification.rb +61 -0
  46. data/lib/chewy/index/syncer.rb +221 -0
  47. data/lib/chewy/{type → index}/witchcraft.rb +100 -39
  48. data/lib/chewy/index/wrapper.rb +95 -0
  49. data/lib/chewy/index.rb +216 -140
  50. data/lib/chewy/journal.rb +66 -0
  51. data/lib/chewy/log_subscriber.rb +8 -8
  52. data/lib/chewy/minitest/helpers.rb +150 -0
  53. data/lib/chewy/minitest/search_index_receiver.rb +76 -0
  54. data/lib/chewy/minitest.rb +1 -0
  55. data/lib/chewy/multi_search.rb +62 -0
  56. data/lib/chewy/railtie.rb +12 -25
  57. data/lib/chewy/rake_helper.rb +335 -37
  58. data/lib/chewy/repository.rb +2 -2
  59. data/lib/chewy/rspec/build_query.rb +12 -0
  60. data/lib/chewy/rspec/helpers.rb +55 -0
  61. data/lib/chewy/rspec/update_index.rb +106 -90
  62. data/lib/chewy/rspec.rb +3 -1
  63. data/lib/chewy/runtime/version.rb +4 -4
  64. data/lib/chewy/runtime.rb +1 -1
  65. data/lib/chewy/search/loader.rb +61 -0
  66. data/lib/chewy/{query → search}/pagination/kaminari.rb +13 -5
  67. data/lib/chewy/search/parameters/aggs.rb +16 -0
  68. data/lib/chewy/search/parameters/allow_partial_search_results.rb +27 -0
  69. data/lib/chewy/search/parameters/collapse.rb +16 -0
  70. data/lib/chewy/search/parameters/concerns/bool_storage.rb +24 -0
  71. data/lib/chewy/search/parameters/concerns/hash_storage.rb +23 -0
  72. data/lib/chewy/search/parameters/concerns/integer_storage.rb +14 -0
  73. data/lib/chewy/search/parameters/concerns/query_storage.rb +238 -0
  74. data/lib/chewy/search/parameters/concerns/string_array_storage.rb +23 -0
  75. data/lib/chewy/search/parameters/concerns/string_storage.rb +14 -0
  76. data/lib/chewy/search/parameters/docvalue_fields.rb +12 -0
  77. data/lib/chewy/search/parameters/explain.rb +16 -0
  78. data/lib/chewy/search/parameters/filter.rb +47 -0
  79. data/lib/chewy/search/parameters/highlight.rb +16 -0
  80. data/lib/chewy/search/parameters/ignore_unavailable.rb +27 -0
  81. data/lib/chewy/search/parameters/indices.rb +78 -0
  82. data/lib/chewy/search/parameters/indices_boost.rb +52 -0
  83. data/lib/chewy/search/parameters/limit.rb +17 -0
  84. data/lib/chewy/search/parameters/load.rb +32 -0
  85. data/lib/chewy/search/parameters/min_score.rb +16 -0
  86. data/lib/chewy/search/parameters/none.rb +25 -0
  87. data/lib/chewy/search/parameters/offset.rb +17 -0
  88. data/lib/chewy/search/parameters/order.rb +51 -0
  89. data/lib/chewy/search/parameters/post_filter.rb +19 -0
  90. data/lib/chewy/search/parameters/preference.rb +16 -0
  91. data/lib/chewy/search/parameters/profile.rb +16 -0
  92. data/lib/chewy/search/parameters/query.rb +19 -0
  93. data/lib/chewy/search/parameters/request_cache.rb +27 -0
  94. data/lib/chewy/search/parameters/rescore.rb +29 -0
  95. data/lib/chewy/search/parameters/script_fields.rb +16 -0
  96. data/lib/chewy/search/parameters/search_after.rb +20 -0
  97. data/lib/chewy/search/parameters/search_type.rb +16 -0
  98. data/lib/chewy/search/parameters/source.rb +77 -0
  99. data/lib/chewy/search/parameters/storage.rb +95 -0
  100. data/lib/chewy/search/parameters/stored_fields.rb +63 -0
  101. data/lib/chewy/search/parameters/suggest.rb +16 -0
  102. data/lib/chewy/search/parameters/terminate_after.rb +16 -0
  103. data/lib/chewy/search/parameters/timeout.rb +16 -0
  104. data/lib/chewy/search/parameters/track_scores.rb +16 -0
  105. data/lib/chewy/search/parameters/track_total_hits.rb +16 -0
  106. data/lib/chewy/search/parameters/version.rb +16 -0
  107. data/lib/chewy/search/parameters.rb +170 -0
  108. data/lib/chewy/search/query_proxy.rb +264 -0
  109. data/lib/chewy/search/request.rb +1071 -0
  110. data/lib/chewy/search/response.rb +119 -0
  111. data/lib/chewy/search/scoping.rb +49 -0
  112. data/lib/chewy/search/scrolling.rb +137 -0
  113. data/lib/chewy/search.rb +68 -28
  114. data/lib/chewy/stash.rb +68 -0
  115. data/lib/chewy/strategy/active_job.rb +3 -2
  116. data/lib/chewy/strategy/atomic.rb +2 -4
  117. data/lib/chewy/strategy/atomic_no_refresh.rb +18 -0
  118. data/lib/chewy/strategy/base.rb +13 -3
  119. data/lib/chewy/strategy/bypass.rb +1 -2
  120. data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +148 -0
  121. data/lib/chewy/strategy/delayed_sidekiq/worker.rb +52 -0
  122. data/lib/chewy/strategy/delayed_sidekiq.rb +17 -0
  123. data/lib/chewy/strategy/lazy_sidekiq.rb +64 -0
  124. data/lib/chewy/strategy/sidekiq.rb +15 -2
  125. data/lib/chewy/strategy/urgent.rb +1 -1
  126. data/lib/chewy/strategy.rb +16 -20
  127. data/lib/chewy/version.rb +1 -1
  128. data/lib/chewy.rb +81 -82
  129. data/lib/generators/chewy/install_generator.rb +3 -3
  130. data/lib/tasks/chewy.rake +99 -32
  131. data/migration_guide.md +56 -0
  132. data/spec/chewy/config_spec.rb +87 -15
  133. data/spec/chewy/fields/base_spec.rb +542 -233
  134. data/spec/chewy/fields/root_spec.rb +115 -17
  135. data/spec/chewy/fields/time_fields_spec.rb +13 -12
  136. data/spec/chewy/index/actions_spec.rb +595 -77
  137. data/spec/chewy/index/adapter/active_record_spec.rb +601 -0
  138. data/spec/chewy/index/adapter/object_spec.rb +243 -0
  139. data/spec/chewy/index/aliases_spec.rb +5 -5
  140. data/spec/chewy/index/import/bulk_builder_spec.rb +494 -0
  141. data/spec/chewy/index/import/bulk_request_spec.rb +95 -0
  142. data/spec/chewy/index/import/journal_builder_spec.rb +87 -0
  143. data/spec/chewy/index/import/routine_spec.rb +110 -0
  144. data/spec/chewy/index/import_spec.rb +615 -0
  145. data/spec/chewy/index/mapping_spec.rb +135 -0
  146. data/spec/chewy/index/observe/active_record_methods_spec.rb +68 -0
  147. data/spec/chewy/index/observe/callback_spec.rb +139 -0
  148. data/spec/chewy/index/observe_spec.rb +143 -0
  149. data/spec/chewy/index/settings_spec.rb +103 -50
  150. data/spec/chewy/index/specification_spec.rb +159 -0
  151. data/spec/chewy/index/syncer_spec.rb +118 -0
  152. data/spec/chewy/index/witchcraft_spec.rb +245 -0
  153. data/spec/chewy/index/wrapper_spec.rb +100 -0
  154. data/spec/chewy/index_spec.rb +149 -121
  155. data/spec/chewy/journal_spec.rb +223 -0
  156. data/spec/chewy/minitest/helpers_spec.rb +198 -0
  157. data/spec/chewy/minitest/search_index_receiver_spec.rb +118 -0
  158. data/spec/chewy/multi_search_spec.rb +84 -0
  159. data/spec/chewy/rake_helper_spec.rb +656 -0
  160. data/spec/chewy/repository_spec.rb +8 -8
  161. data/spec/chewy/rspec/build_query_spec.rb +34 -0
  162. data/spec/chewy/rspec/helpers_spec.rb +61 -0
  163. data/spec/chewy/rspec/update_index_spec.rb +220 -114
  164. data/spec/chewy/runtime_spec.rb +2 -2
  165. data/spec/chewy/search/loader_spec.rb +83 -0
  166. data/spec/chewy/search/pagination/kaminari_examples.rb +69 -0
  167. data/spec/chewy/search/pagination/kaminari_spec.rb +21 -0
  168. data/spec/chewy/search/parameters/aggs_spec.rb +5 -0
  169. data/spec/chewy/search/parameters/bool_storage_examples.rb +53 -0
  170. data/spec/chewy/search/parameters/collapse_spec.rb +5 -0
  171. data/spec/chewy/search/parameters/docvalue_fields_spec.rb +5 -0
  172. data/spec/chewy/search/parameters/explain_spec.rb +5 -0
  173. data/spec/chewy/search/parameters/filter_spec.rb +5 -0
  174. data/spec/chewy/search/parameters/hash_storage_examples.rb +59 -0
  175. data/spec/chewy/search/parameters/highlight_spec.rb +5 -0
  176. data/spec/chewy/search/parameters/ignore_unavailable_spec.rb +67 -0
  177. data/spec/chewy/search/parameters/indices_spec.rb +99 -0
  178. data/spec/chewy/search/parameters/integer_storage_examples.rb +32 -0
  179. data/spec/chewy/search/parameters/limit_spec.rb +5 -0
  180. data/spec/chewy/search/parameters/load_spec.rb +60 -0
  181. data/spec/chewy/search/parameters/min_score_spec.rb +32 -0
  182. data/spec/chewy/search/parameters/none_spec.rb +5 -0
  183. data/spec/chewy/search/parameters/offset_spec.rb +5 -0
  184. data/spec/chewy/search/parameters/order_spec.rb +72 -0
  185. data/spec/chewy/search/parameters/post_filter_spec.rb +5 -0
  186. data/spec/chewy/search/parameters/preference_spec.rb +5 -0
  187. data/spec/chewy/search/parameters/profile_spec.rb +5 -0
  188. data/spec/chewy/search/parameters/query_spec.rb +5 -0
  189. data/spec/chewy/search/parameters/query_storage_examples.rb +434 -0
  190. data/spec/chewy/search/parameters/request_cache_spec.rb +67 -0
  191. data/spec/chewy/search/parameters/rescore_spec.rb +62 -0
  192. data/spec/chewy/search/parameters/script_fields_spec.rb +5 -0
  193. data/spec/chewy/search/parameters/search_after_spec.rb +35 -0
  194. data/spec/chewy/search/parameters/search_type_spec.rb +5 -0
  195. data/spec/chewy/search/parameters/source_spec.rb +162 -0
  196. data/spec/chewy/search/parameters/storage_spec.rb +60 -0
  197. data/spec/chewy/search/parameters/stored_fields_spec.rb +126 -0
  198. data/spec/chewy/search/parameters/string_array_storage_examples.rb +63 -0
  199. data/spec/chewy/search/parameters/string_storage_examples.rb +32 -0
  200. data/spec/chewy/search/parameters/suggest_spec.rb +5 -0
  201. data/spec/chewy/search/parameters/terminate_after_spec.rb +5 -0
  202. data/spec/chewy/search/parameters/timeout_spec.rb +5 -0
  203. data/spec/chewy/search/parameters/track_scores_spec.rb +5 -0
  204. data/spec/chewy/search/parameters/track_total_hits_spec.rb +5 -0
  205. data/spec/chewy/search/parameters/version_spec.rb +5 -0
  206. data/spec/chewy/search/parameters_spec.rb +161 -0
  207. data/spec/chewy/search/query_proxy_spec.rb +119 -0
  208. data/spec/chewy/search/request_spec.rb +880 -0
  209. data/spec/chewy/search/response_spec.rb +202 -0
  210. data/spec/chewy/search/scrolling_spec.rb +171 -0
  211. data/spec/chewy/search_spec.rb +82 -55
  212. data/spec/chewy/stash_spec.rb +85 -0
  213. data/spec/chewy/strategy/active_job_spec.rb +27 -8
  214. data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
  215. data/spec/chewy/strategy/atomic_spec.rb +13 -11
  216. data/spec/chewy/strategy/delayed_sidekiq_spec.rb +190 -0
  217. data/spec/chewy/strategy/lazy_sidekiq_spec.rb +214 -0
  218. data/spec/chewy/strategy/sidekiq_spec.rb +19 -7
  219. data/spec/chewy/strategy_spec.rb +19 -15
  220. data/spec/chewy_spec.rb +65 -88
  221. data/spec/spec_helper.rb +11 -20
  222. data/spec/support/active_record.rb +48 -6
  223. data/spec/support/class_helpers.rb +4 -19
  224. metadata +299 -183
  225. data/.travis.yml +0 -76
  226. data/Appraisals +0 -76
  227. data/gemfiles/rails.3.2.activerecord.gemfile +0 -15
  228. data/gemfiles/rails.3.2.activerecord.kaminari.gemfile +0 -14
  229. data/gemfiles/rails.3.2.activerecord.will_paginate.gemfile +0 -14
  230. data/gemfiles/rails.4.0.activerecord.gemfile +0 -15
  231. data/gemfiles/rails.4.0.activerecord.kaminari.gemfile +0 -14
  232. data/gemfiles/rails.4.0.activerecord.will_paginate.gemfile +0 -14
  233. data/gemfiles/rails.4.0.mongoid.4.0.0.gemfile +0 -15
  234. data/gemfiles/rails.4.0.mongoid.4.0.0.kaminari.gemfile +0 -14
  235. data/gemfiles/rails.4.0.mongoid.4.0.0.will_paginate.gemfile +0 -14
  236. data/gemfiles/rails.4.0.mongoid.5.1.0.gemfile +0 -15
  237. data/gemfiles/rails.4.0.mongoid.5.1.0.kaminari.gemfile +0 -14
  238. data/gemfiles/rails.4.0.mongoid.5.1.0.will_paginate.gemfile +0 -14
  239. data/gemfiles/rails.4.1.activerecord.gemfile +0 -15
  240. data/gemfiles/rails.4.1.activerecord.kaminari.gemfile +0 -14
  241. data/gemfiles/rails.4.1.activerecord.will_paginate.gemfile +0 -14
  242. data/gemfiles/rails.4.1.mongoid.4.0.0.gemfile +0 -15
  243. data/gemfiles/rails.4.1.mongoid.4.0.0.kaminari.gemfile +0 -14
  244. data/gemfiles/rails.4.1.mongoid.4.0.0.will_paginate.gemfile +0 -14
  245. data/gemfiles/rails.4.1.mongoid.5.1.0.gemfile +0 -15
  246. data/gemfiles/rails.4.1.mongoid.5.1.0.kaminari.gemfile +0 -14
  247. data/gemfiles/rails.4.1.mongoid.5.1.0.will_paginate.gemfile +0 -14
  248. data/gemfiles/rails.4.2.activerecord.gemfile +0 -16
  249. data/gemfiles/rails.4.2.activerecord.kaminari.gemfile +0 -15
  250. data/gemfiles/rails.4.2.activerecord.will_paginate.gemfile +0 -15
  251. data/gemfiles/rails.4.2.mongoid.4.0.0.gemfile +0 -15
  252. data/gemfiles/rails.4.2.mongoid.4.0.0.kaminari.gemfile +0 -14
  253. data/gemfiles/rails.4.2.mongoid.4.0.0.will_paginate.gemfile +0 -14
  254. data/gemfiles/rails.4.2.mongoid.5.1.0.gemfile +0 -15
  255. data/gemfiles/rails.4.2.mongoid.5.1.0.kaminari.gemfile +0 -14
  256. data/gemfiles/rails.4.2.mongoid.5.1.0.will_paginate.gemfile +0 -14
  257. data/gemfiles/rails.5.0.0.beta3.activerecord.gemfile +0 -16
  258. data/gemfiles/rails.5.0.0.beta3.activerecord.kaminari.gemfile +0 -16
  259. data/gemfiles/rails.5.0.0.beta3.activerecord.will_paginate.gemfile +0 -15
  260. data/gemfiles/sequel.4.31.gemfile +0 -13
  261. data/lib/chewy/backports/deep_dup.rb +0 -46
  262. data/lib/chewy/backports/duplicable.rb +0 -90
  263. data/lib/chewy/query/compose.rb +0 -69
  264. data/lib/chewy/query/criteria.rb +0 -181
  265. data/lib/chewy/query/filters.rb +0 -227
  266. data/lib/chewy/query/loading.rb +0 -111
  267. data/lib/chewy/query/nodes/and.rb +0 -25
  268. data/lib/chewy/query/nodes/base.rb +0 -17
  269. data/lib/chewy/query/nodes/bool.rb +0 -32
  270. data/lib/chewy/query/nodes/equal.rb +0 -34
  271. data/lib/chewy/query/nodes/exists.rb +0 -20
  272. data/lib/chewy/query/nodes/expr.rb +0 -28
  273. data/lib/chewy/query/nodes/field.rb +0 -106
  274. data/lib/chewy/query/nodes/has_child.rb +0 -14
  275. data/lib/chewy/query/nodes/has_parent.rb +0 -14
  276. data/lib/chewy/query/nodes/has_relation.rb +0 -61
  277. data/lib/chewy/query/nodes/match_all.rb +0 -11
  278. data/lib/chewy/query/nodes/missing.rb +0 -20
  279. data/lib/chewy/query/nodes/not.rb +0 -25
  280. data/lib/chewy/query/nodes/or.rb +0 -25
  281. data/lib/chewy/query/nodes/prefix.rb +0 -18
  282. data/lib/chewy/query/nodes/query.rb +0 -20
  283. data/lib/chewy/query/nodes/range.rb +0 -63
  284. data/lib/chewy/query/nodes/raw.rb +0 -15
  285. data/lib/chewy/query/nodes/regexp.rb +0 -31
  286. data/lib/chewy/query/nodes/script.rb +0 -20
  287. data/lib/chewy/query/pagination/will_paginate.rb +0 -27
  288. data/lib/chewy/query/pagination.rb +0 -16
  289. data/lib/chewy/query/scoping.rb +0 -20
  290. data/lib/chewy/query.rb +0 -1026
  291. data/lib/chewy/strategy/resque.rb +0 -26
  292. data/lib/chewy/type/actions.rb +0 -19
  293. data/lib/chewy/type/adapter/active_record.rb +0 -72
  294. data/lib/chewy/type/adapter/mongoid.rb +0 -58
  295. data/lib/chewy/type/adapter/object.rb +0 -89
  296. data/lib/chewy/type/adapter/orm.rb +0 -156
  297. data/lib/chewy/type/adapter/sequel.rb +0 -75
  298. data/lib/chewy/type/crutch.rb +0 -31
  299. data/lib/chewy/type/import.rb +0 -224
  300. data/lib/chewy/type/observe.rb +0 -76
  301. data/lib/chewy/type/wrapper.rb +0 -53
  302. data/lib/chewy/type.rb +0 -89
  303. data/lib/sequel/plugins/chewy_observe.rb +0 -78
  304. data/spec/chewy/query/criteria_spec.rb +0 -433
  305. data/spec/chewy/query/filters_spec.rb +0 -173
  306. data/spec/chewy/query/loading_spec.rb +0 -86
  307. data/spec/chewy/query/nodes/and_spec.rb +0 -16
  308. data/spec/chewy/query/nodes/bool_spec.rb +0 -22
  309. data/spec/chewy/query/nodes/equal_spec.rb +0 -32
  310. data/spec/chewy/query/nodes/exists_spec.rb +0 -18
  311. data/spec/chewy/query/nodes/has_child_spec.rb +0 -40
  312. data/spec/chewy/query/nodes/has_parent_spec.rb +0 -40
  313. data/spec/chewy/query/nodes/match_all_spec.rb +0 -11
  314. data/spec/chewy/query/nodes/missing_spec.rb +0 -15
  315. data/spec/chewy/query/nodes/not_spec.rb +0 -16
  316. data/spec/chewy/query/nodes/or_spec.rb +0 -16
  317. data/spec/chewy/query/nodes/prefix_spec.rb +0 -16
  318. data/spec/chewy/query/nodes/query_spec.rb +0 -12
  319. data/spec/chewy/query/nodes/range_spec.rb +0 -32
  320. data/spec/chewy/query/nodes/raw_spec.rb +0 -11
  321. data/spec/chewy/query/nodes/regexp_spec.rb +0 -31
  322. data/spec/chewy/query/nodes/script_spec.rb +0 -15
  323. data/spec/chewy/query/pagination/kaminari_spec.rb +0 -57
  324. data/spec/chewy/query/pagination/will_paginage_spec.rb +0 -60
  325. data/spec/chewy/query/pagination_spec.rb +0 -36
  326. data/spec/chewy/query_spec.rb +0 -632
  327. data/spec/chewy/strategy/resque_spec.rb +0 -40
  328. data/spec/chewy/type/actions_spec.rb +0 -31
  329. data/spec/chewy/type/adapter/active_record_spec.rb +0 -317
  330. data/spec/chewy/type/adapter/mongoid_spec.rb +0 -253
  331. data/spec/chewy/type/adapter/object_spec.rb +0 -139
  332. data/spec/chewy/type/adapter/sequel_spec.rb +0 -320
  333. data/spec/chewy/type/import_spec.rb +0 -433
  334. data/spec/chewy/type/mapping_spec.rb +0 -106
  335. data/spec/chewy/type/observe_spec.rb +0 -127
  336. data/spec/chewy/type/witchcraft_spec.rb +0 -154
  337. data/spec/chewy/type/wrapper_spec.rb +0 -58
  338. data/spec/chewy/type_spec.rb +0 -33
  339. data/spec/support/mongoid.rb +0 -81
  340. data/spec/support/sequel.rb +0 -75
data/lib/chewy/query.rb DELETED
@@ -1,1026 +0,0 @@
1
- require 'chewy/query/criteria'
2
- require 'chewy/query/filters'
3
- require 'chewy/query/scoping'
4
- require 'chewy/query/loading'
5
- require 'chewy/query/pagination'
6
-
7
- module Chewy
8
- # Query allows you to create ES search requests with convenient
9
- # chainable DSL. Queries are lazy evaluated and might be merged.
10
- # The same DSL is used for whole index or individual types query build.
11
- #
12
- # UsersIndex.filter{ age < 42 }.query(text: {name: 'Alex'}).limit(20)
13
- # UsersIndex::User.filter{ age < 42 }.query(text: {name: 'Alex'}).limit(20)
14
- #
15
- class Query
16
- include Enumerable
17
- include Scoping
18
- include Loading
19
- include Pagination
20
-
21
- delegate :each, :count, :size, to: :_collection
22
- alias_method :to_ary, :to_a
23
-
24
- attr_reader :_indexes, :_types, :options, :criteria
25
-
26
- def initialize *indexes_or_types_and_options
27
- @options = indexes_or_types_and_options.extract_options!
28
- @_types = indexes_or_types_and_options.select { |klass| klass < Chewy::Type }
29
- @_indexes = indexes_or_types_and_options.select { |klass| klass < Chewy::Index }
30
- @_indexes |= @_types.map(&:index)
31
- @criteria = Criteria.new
32
- end
33
-
34
- # Comparation with other query or collection
35
- # If other is collection - search request is executed and
36
- # result is used for comparation
37
- #
38
- # UsersIndex.filter(term: {name: 'Johny'}) == UsersIndex.filter(term: {name: 'Johny'}) # => true
39
- # UsersIndex.filter(term: {name: 'Johny'}) == UsersIndex.filter(term: {name: 'Johny'}).to_a # => true
40
- # UsersIndex.filter(term: {name: 'Johny'}) == UsersIndex.filter(term: {name: 'Winnie'}) # => false
41
- #
42
- def == other
43
- super || if other.is_a?(self.class)
44
- other.criteria == criteria
45
- else
46
- to_a == other
47
- end
48
- end
49
-
50
- # Adds <tt>explain</tt> parameter to search request.
51
- #
52
- # UsersIndex.filter(term: {name: 'Johny'}).explain
53
- # UsersIndex.filter(term: {name: 'Johny'}).explain(true)
54
- # UsersIndex.filter(term: {name: 'Johny'}).explain(false)
55
- #
56
- # Calling explain without any arguments sets explanation flag to true.
57
- # With <tt>explain: true</tt>, every result object has <tt>_explanation</tt>
58
- # method
59
- #
60
- # UsersIndex::User.filter(term: {name: 'Johny'}).explain.first._explanation # => {...}
61
- #
62
- def explain value = nil
63
- chain { criteria.update_request_options explain: (value.nil? ? true : value) }
64
- end
65
-
66
- # Adds <tt>script_fields</tt> parameter to search request.
67
- # UsersIndex.script_fields(
68
- # distance: {
69
- # params: {
70
- # lat: 37.569976,
71
- # lon: -122.351591
72
- # },
73
- # script: "doc['coordinates'].distanceInMiles(lat, lon)"
74
- # }
75
- # )
76
- def script_fields value
77
- chain { criteria.update_script_fields(value) }
78
- end
79
-
80
- # Sets query compilation mode for search request.
81
- # Not used if only one filter for search is specified.
82
- # Possible values:
83
- #
84
- # * <tt>:must</tt>
85
- # Default value. Query compiles into a bool <tt>must</tt> query.
86
- #
87
- # Ex:
88
- #
89
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}})
90
- # # => {body: {
91
- # query: {bool: {must: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
92
- # }}
93
- #
94
- # * <tt>:should</tt>
95
- # Query compiles into a bool <tt>should</tt> query.
96
- #
97
- # Ex:
98
- #
99
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(:should)
100
- # # => {body: {
101
- # query: {bool: {should: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
102
- # }}
103
- #
104
- # * Any acceptable <tt>minimum_should_match</tt> value (1, '2', '75%')
105
- # Query compiles into a bool <tt>should</tt> query with <tt>minimum_should_match</tt> set.
106
- #
107
- # Ex:
108
- #
109
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode('50%')
110
- # # => {body: {
111
- # query: {bool: {
112
- # should: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
113
- # minimum_should_match: '50%'
114
- # }}
115
- # }}
116
- #
117
- # * <tt>:dis_max</tt>
118
- # Query compiles into a <tt>dis_max</tt> query.
119
- #
120
- # Ex:
121
- #
122
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(:dis_max)
123
- # # => {body: {
124
- # query: {dis_max: {queries: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
125
- # }}
126
- #
127
- # * Any Float value (0.0, 0.7, 1.0)
128
- # Query compiles into a <tt>dis_max</tt> query with <tt>tie_breaker</tt> option set.
129
- #
130
- # Ex:
131
- #
132
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(0.7)
133
- # # => {body: {
134
- # query: {dis_max: {
135
- # queries: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
136
- # tie_breaker: 0.7
137
- # }}
138
- # }}
139
- #
140
- # Default value for <tt>:query_mode</tt> might be changed
141
- # with <tt>Chewy.query_mode</tt> config option.
142
- #
143
- # Chewy.query_mode = :dis_max
144
- # Chewy.query_mode = '50%'
145
- #
146
- def query_mode value
147
- chain { criteria.update_options query_mode: value }
148
- end
149
-
150
- # Sets query compilation mode for search request.
151
- # Not used if only one filter for search is specified.
152
- # Possible values:
153
- #
154
- # * <tt>:and</tt>
155
- # Default value. Filter compiles into an <tt>and</tt> filter.
156
- #
157
- # Ex:
158
- #
159
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }
160
- # # => {body: {query: {filtered: {
161
- # query: {...},
162
- # filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
163
- # }}}}
164
- #
165
- # * <tt>:or</tt>
166
- # Filter compiles into an <tt>or</tt> filter.
167
- #
168
- # Ex:
169
- #
170
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:or)
171
- # # => {body: {query: {filtered: {
172
- # query: {...},
173
- # filter: {or: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
174
- # }}}}
175
- #
176
- # * <tt>:must</tt>
177
- # Filter compiles into a bool <tt>must</tt> filter.
178
- #
179
- # Ex:
180
- #
181
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:must)
182
- # # => {body: {query: {filtered: {
183
- # query: {...},
184
- # filter: {bool: {must: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
185
- # }}}}
186
- #
187
- # * <tt>:should</tt>
188
- # Filter compiles into a bool <tt>should</tt> filter.
189
- #
190
- # Ex:
191
- #
192
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:should)
193
- # # => {body: {query: {filtered: {
194
- # query: {...},
195
- # filter: {bool: {should: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
196
- # }}}}
197
- #
198
- # * Any acceptable <tt>minimum_should_match</tt> value (1, '2', '75%')
199
- # Filter compiles into bool <tt>should</tt> filter with <tt>minimum_should_match</tt> set.
200
- #
201
- # Ex:
202
- #
203
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode('50%')
204
- # # => {body: {query: {filtered: {
205
- # query: {...},
206
- # filter: {bool: {
207
- # should: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
208
- # minimum_should_match: '50%'
209
- # }}
210
- # }}}}
211
- #
212
- # Default value for <tt>:filter_mode</tt> might be changed
213
- # with <tt>Chewy.filter_mode</tt> config option.
214
- #
215
- # Chewy.filter_mode = :should
216
- # Chewy.filter_mode = '50%'
217
- #
218
- def filter_mode value
219
- chain { criteria.update_options filter_mode: value }
220
- end
221
-
222
- # Acts the same way as `filter_mode`, but used for `post_filter`.
223
- # Note that it fallbacks by default to `Chewy.filter_mode` if
224
- # `Chewy.post_filter_mode` is nil.
225
- #
226
- # UsersIndex.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode(:and)
227
- # UsersIndex.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode(:should)
228
- # UsersIndex.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode('50%')
229
- #
230
- def post_filter_mode value
231
- chain { criteria.update_options post_filter_mode: value }
232
- end
233
-
234
- # A search timeout, bounding the search request to be executed within the
235
- # specified time value and bail with the hits accumulated up to that point
236
- # when expired. Defaults to no timeout.
237
- #
238
- # By default, the coordinating node waits to receive a response from all
239
- # shards. If one node is having trouble, it could slow down the response to
240
- # all search requests.
241
- #
242
- # The timeout parameter tells the coordinating node how long it should wait
243
- # before giving up and just returning the results that it already has. It
244
- # can be better to return some results than none at all.
245
- #
246
- # The response to a search request will indicate whether the search timed
247
- # out and how many shards responded successfully:
248
- #
249
- # ...
250
- # "timed_out": true,
251
- # "_shards": {
252
- # "total": 5,
253
- # "successful": 4,
254
- # "failed": 1
255
- # },
256
- # ...
257
- #
258
- # The primary shard assigned to perform the index operation might not be
259
- # available when the index operation is executed. Some reasons for this
260
- # might be that the primary shard is currently recovering from a gateway or
261
- # undergoing relocation. By default, the index operation will wait on the
262
- # primary shard to become available for up to 1 minute before failing and
263
- # responding with an error. The timeout parameter can be used to explicitly
264
- # specify how long it waits.
265
- #
266
- # UsersIndex.timeout("5000ms")
267
- #
268
- # Timeout is not a circuit breaker.
269
- #
270
- # It should be noted that this timeout does not halt the execution of the
271
- # query, it merely tells the coordinating node to return the results
272
- # collected so far and to close the connection. In the background, other
273
- # shards may still be processing the query even though results have been
274
- # sent.
275
- #
276
- # Use the timeout because it is important to your SLA, not because you want
277
- # to abort the execution of long running queries.
278
- #
279
- def timeout value
280
- chain { criteria.update_request_options timeout: value }
281
- end
282
-
283
- # Sets elasticsearch <tt>size</tt> search request param
284
- # Default value is set in the elasticsearch and is 10.
285
- #
286
- # UsersIndex.filter{ name == 'Johny' }.limit(100)
287
- # # => {body: {
288
- # query: {...},
289
- # size: 100
290
- # }}
291
- #
292
- def limit value
293
- chain { criteria.update_request_options size: Integer(value) }
294
- end
295
-
296
- # Sets elasticsearch <tt>from</tt> search request param
297
- #
298
- # UsersIndex.filter{ name == 'Johny' }.offset(300)
299
- # # => {body: {
300
- # query: {...},
301
- # from: 300
302
- # }}
303
- #
304
- def offset value
305
- chain { criteria.update_request_options from: Integer(value) }
306
- end
307
-
308
- # Elasticsearch highlight query option support
309
- #
310
- # UsersIndex.query(...).highlight(fields: { ... })
311
- #
312
- def highlight value
313
- chain { criteria.update_request_options highlight: value }
314
- end
315
-
316
- # Elasticsearch rescore query option support
317
- #
318
- # UsersIndex.query(...).rescore(query: { ... })
319
- #
320
- def rescore value
321
- chain { criteria.update_request_options rescore: value }
322
- end
323
-
324
- # Elasticsearch minscore option support
325
- #
326
- # UsersIndex.query(...).min_score(0.5)
327
- #
328
- def min_score value
329
- chain { criteria.update_request_options min_score: value }
330
- end
331
-
332
- # Adds facets section to the search request.
333
- # All the chained facets a merged and added to the
334
- # search request
335
- #
336
- # UsersIndex.facets(tags: {terms: {field: 'tags'}}).facets(ages: {terms: {field: 'age'}})
337
- # # => {body: {
338
- # query: {...},
339
- # facets: {tags: {terms: {field: 'tags'}}, ages: {terms: {field: 'age'}}}
340
- # }}
341
- #
342
- # If called parameterless - returns result facets from ES performing request.
343
- # Returns empty hash if no facets was requested or resulted.
344
- #
345
- def facets params = nil
346
- raise RemovedFeature, 'removed in elasticsearch 2.0' if Runtime.version >= '2.0'
347
- if params
348
- chain { criteria.update_facets params }
349
- else
350
- _response['facets'] || {}
351
- end
352
- end
353
-
354
- # Adds a script function to score the search request. All scores are
355
- # added to the search request and combinded according to
356
- # <tt>boost_mode</tt> and <tt>score_mode</tt>
357
- #
358
- # UsersIndex.script_score("doc['boost'].value", params: { modifier: 2 })
359
- # # => {body:
360
- # query: {
361
- # function_score: {
362
- # query: { ...},
363
- # functions: [{
364
- # script_score: {
365
- # script: "doc['boost'].value * modifier",
366
- # params: { modifier: 2 }
367
- # }
368
- # }
369
- # }]
370
- # } } }
371
- def script_score(script, options = {})
372
- scoring = { script_score: { script: script }.merge(options) }
373
- chain { criteria.update_scores scoring }
374
- end
375
-
376
- # Adds a boost factor to the search request. All scores are
377
- # added to the search request and combinded according to
378
- # <tt>boost_mode</tt> and <tt>score_mode</tt>
379
- #
380
- # This probably only makes sense if you specify a filter
381
- # for the boost factor as well
382
- #
383
- # UsersIndex.boost_factor(23, filter: { term: { foo: :bar} })
384
- # # => {body:
385
- # query: {
386
- # function_score: {
387
- # query: { ...},
388
- # functions: [{
389
- # boost_factor: 23,
390
- # filter: { term: { foo: :bar } }
391
- # }]
392
- # } } }
393
- def boost_factor(factor, options = {})
394
- scoring = options.merge(boost_factor: factor.to_i)
395
- chain { criteria.update_scores scoring }
396
- end
397
-
398
- # Adds a random score to the search request. All scores are
399
- # added to the search request and combinded according to
400
- # <tt>boost_mode</tt> and <tt>score_mode</tt>
401
- #
402
- # This probably only makes sense if you specify a filter
403
- # for the random score as well.
404
- #
405
- # If you do not pass in a seed value, Time.now will be used
406
- #
407
- # UsersIndex.random_score(23, filter: { foo: :bar})
408
- # # => {body:
409
- # query: {
410
- # function_score: {
411
- # query: { ...},
412
- # functions: [{
413
- # random_score: { seed: 23 },
414
- # filter: { foo: :bar }
415
- # }]
416
- # } } }
417
- def random_score(seed = Time.now, options = {})
418
- scoring = options.merge(random_score: { seed: seed.to_i })
419
- chain { criteria.update_scores scoring }
420
- end
421
-
422
- # Add a field value scoring to the search. All scores are
423
- # added to the search request and combinded according to
424
- # <tt>boost_mode</tt> and <tt>score_mode</tt>
425
- #
426
- # This function is only available in Elasticsearch 1.2 and
427
- # greater
428
- #
429
- # UsersIndex.field_value_factor(
430
- # {
431
- # field: :boost,
432
- # factor: 1.2,
433
- # modifier: :sqrt
434
- # }, filter: { foo: :bar})
435
- # # => {body:
436
- # query: {
437
- # function_score: {
438
- # query: { ...},
439
- # functions: [{
440
- # field_value_factor: {
441
- # field: :boost,
442
- # factor: 1.2,
443
- # modifier: :sqrt
444
- # },
445
- # filter: { foo: :bar }
446
- # }]
447
- # } } }
448
- def field_value_factor(settings, options = {})
449
- scoring = options.merge(field_value_factor: settings)
450
- chain { criteria.update_scores scoring }
451
- end
452
-
453
- # Add a decay scoring to the search. All scores are
454
- # added to the search request and combinded according to
455
- # <tt>boost_mode</tt> and <tt>score_mode</tt>
456
- #
457
- # The parameters have default values, but those may not
458
- # be very useful for most applications.
459
- #
460
- # UsersIndex.decay(
461
- # :gauss,
462
- # :field,
463
- # origin: '11, 12',
464
- # scale: '2km',
465
- # offset: '5km',
466
- # decay: 0.4,
467
- # filter: { foo: :bar})
468
- # # => {body:
469
- # query: {
470
- # gauss: {
471
- # query: { ...},
472
- # functions: [{
473
- # gauss: {
474
- # field: {
475
- # origin: '11, 12',
476
- # scale: '2km',
477
- # offset: '5km',
478
- # decay: 0.4
479
- # }
480
- # },
481
- # filter: { foo: :bar }
482
- # }]
483
- # } } }
484
- def decay(function, field, options = {})
485
- field_options = options.extract!(:origin, :scale, :offset, :decay).delete_if { |_, v| v.nil? }
486
- scoring = options.merge(function => {
487
- field => field_options
488
- })
489
- chain { criteria.update_scores scoring }
490
- end
491
-
492
- # Sets elasticsearch <tt>aggregations</tt> search request param
493
- #
494
- # UsersIndex.filter{ name == 'Johny' }.aggregations(category_id: {terms: {field: 'category_ids'}})
495
- # # => {body: {
496
- # query: {...},
497
- # aggregations: {
498
- # terms: {
499
- # field: 'category_ids'
500
- # }
501
- # }
502
- # }}
503
- #
504
- def aggregations params = nil
505
- @_named_aggs ||= _build_named_aggs
506
- @_fully_qualified_named_aggs ||= _build_fqn_aggs
507
- if params
508
- params = { params => @_named_aggs[params] } if params.is_a?(Symbol)
509
- params = { params => _get_fully_qualified_named_agg(params) } if params.is_a?(String) && params =~ /\A\S+#\S+\.\S+\z/
510
- chain { criteria.update_aggregations params }
511
- else
512
- _response['aggregations'] || {}
513
- end
514
- end
515
- alias :aggs :aggregations
516
-
517
- # In this simplest of implementations each named aggregation must be uniquely named
518
- def _build_named_aggs
519
- named_aggs = {}
520
- @_indexes.each do |index|
521
- index.types.each do |type|
522
- type._agg_defs.each do |agg_name, prc|
523
- named_aggs[agg_name] = prc.call
524
- end
525
- end
526
- end
527
- named_aggs
528
- end
529
-
530
- def _build_fqn_aggs
531
- named_aggs = {}
532
- @_indexes.each do |index|
533
- named_aggs[index.to_s.downcase] ||= {}
534
- index.types.each do |type|
535
- named_aggs[index.to_s.downcase][type.to_s.downcase] ||= {}
536
- type._agg_defs.each do |agg_name, prc|
537
- named_aggs[index.to_s.downcase][type.to_s.downcase][agg_name.to_s.downcase] = prc.call
538
- end
539
- end
540
- end
541
- named_aggs
542
- end
543
-
544
- def _get_fully_qualified_named_agg(str)
545
- parts = str.scan(/\A(\S+)#(\S+)\.(\S+)\z/).first
546
- idx = "#{parts[0]}index"
547
- type = "#{idx}::#{parts[1]}"
548
- agg_name = parts[2]
549
- @_fully_qualified_named_aggs[idx][type][agg_name]
550
- end
551
-
552
- # Sets elasticsearch <tt>suggest</tt> search request param
553
- #
554
- # UsersIndex.suggest(name: {text: 'Joh', term: {field: 'name'}})
555
- # # => {body: {
556
- # query: {...},
557
- # suggest: {
558
- # text: 'Joh',
559
- # term: {
560
- # field: 'name'
561
- # }
562
- # }
563
- # }}
564
- #
565
- def suggest params = nil
566
- if params
567
- chain { criteria.update_suggest params }
568
- else
569
- _response['suggest'] || {}
570
- end
571
- end
572
-
573
- # Marks the criteria as having zero records. This scope always returns empty array
574
- # without touching the elasticsearch server.
575
- # All the chained calls of methods don't affect the result
576
- #
577
- # UsersIndex.none.to_a
578
- # # => []
579
- # UsersIndex.query(text: {name: 'Johny'}).none.to_a
580
- # # => []
581
- # UsersIndex.none.query(text: {name: 'Johny'}).to_a
582
- # # => []
583
- #
584
- def none
585
- chain { criteria.update_options none: true }
586
- end
587
-
588
- # Setups strategy for top-level filtered query
589
- #
590
- # UsersIndex.filter { name == 'Johny'}.strategy(:leap_frog)
591
- # # => {body: {
592
- # query: { filtered: {
593
- # filter: { term: { name: 'Johny' } },
594
- # strategy: 'leap_frog'
595
- # } }
596
- # }}
597
- #
598
- def strategy value = nil
599
- chain { criteria.update_options strategy: value }
600
- end
601
-
602
- # Adds one or more query to the search request
603
- # Internally queries are stored as an array
604
- # While the full query compilation this array compiles
605
- # according to <tt>:query_mode</tt> option value
606
- #
607
- # By default it joines inside <tt>must</tt> query
608
- # See <tt>#query_mode</tt> chainable method for more info.
609
- #
610
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}})
611
- # UsersIndex::User.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}})
612
- # # => {body: {
613
- # query: {bool: {must: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
614
- # }}
615
- #
616
- # If only one query was specified, it will become a result
617
- # query as is, without joining.
618
- #
619
- # UsersIndex.query(text: {name: 'Johny'})
620
- # # => {body: {
621
- # query: {text: {name: 'Johny'}}
622
- # }}
623
- #
624
- def query params
625
- chain { criteria.update_queries params }
626
- end
627
-
628
- # Adds one or more filter to the search request
629
- # Internally filters are stored as an array
630
- # While the full query compilation this array compiles
631
- # according to <tt>:filter_mode</tt> option value
632
- #
633
- # By default it joins inside <tt>and</tt> filter
634
- # See <tt>#filter_mode</tt> chainable method for more info.
635
- #
636
- # Also this method supports block DSL.
637
- # See <tt>Chewy::Query::Filters</tt> for more info.
638
- #
639
- # UsersIndex.filter(term: {name: 'Johny'}).filter(range: {age: {lte: 42}})
640
- # UsersIndex::User.filter(term: {name: 'Johny'}).filter(range: {age: {lte: 42}})
641
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }
642
- # # => {body: {query: {filtered: {
643
- # query: {...},
644
- # filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
645
- # }}}}
646
- #
647
- # If only one filter was specified, it will become a result
648
- # filter as is, without joining.
649
- #
650
- # UsersIndex.filter(term: {name: 'Johny'})
651
- # # => {body: {query: {filtered: {
652
- # query: {...},
653
- # filter: {term: {name: 'Johny'}}
654
- # }}}}
655
- #
656
- def filter params = nil, &block
657
- params = Filters.new(&block).__render__ if block
658
- chain { criteria.update_filters params }
659
- end
660
-
661
- # Adds one or more post_filter to the search request
662
- # Internally post_filters are stored as an array
663
- # While the full query compilation this array compiles
664
- # according to <tt>:post_filter_mode</tt> option value
665
- #
666
- # By default it joins inside <tt>and</tt> filter
667
- # See <tt>#post_filter_mode</tt> chainable method for more info.
668
- #
669
- # Also this method supports block DSL.
670
- # See <tt>Chewy::Query::Filters</tt> for more info.
671
- #
672
- # UsersIndex.post_filter(term: {name: 'Johny'}).post_filter(range: {age: {lte: 42}})
673
- # UsersIndex::User.post_filter(term: {name: 'Johny'}).post_filter(range: {age: {lte: 42}})
674
- # UsersIndex.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }
675
- # # => {body: {
676
- # post_filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
677
- # }}
678
- #
679
- # If only one post_filter was specified, it will become a result
680
- # post_filter as is, without joining.
681
- #
682
- # UsersIndex.post_filter(term: {name: 'Johny'})
683
- # # => {body: {
684
- # post_filter: {term: {name: 'Johny'}}
685
- # }}
686
- #
687
- def post_filter params = nil, &block
688
- params = Filters.new(&block).__render__ if block
689
- chain { criteria.update_post_filters params }
690
- end
691
-
692
- # Sets the boost mode for custom scoring/boosting.
693
- # Not used if no score functions are specified
694
- # Possible values:
695
- #
696
- # * <tt>:multiply</tt>
697
- # Default value. Query score and function result are multiplied.
698
- #
699
- # Ex:
700
- #
701
- # UsersIndex.boost_mode('multiply').script_score('doc['boost'].value')
702
- # # => {body: {query: function_score: {
703
- # query: {...},
704
- # boost_mode: 'multiply',
705
- # functions: [ ... ]
706
- # }}}
707
- #
708
- # * <tt>:replace</tt>
709
- # Only function result is used, query score is ignored.
710
- #
711
- # * <tt>:sum</tt>
712
- # Query score and function score are added.
713
- #
714
- # * <tt>:avg</tt>
715
- # Average of query and function score.
716
- #
717
- # * <tt>:max</tt>
718
- # Max of query and function score.
719
- #
720
- # * <tt>:min</tt>
721
- # Min of query and function score.
722
- #
723
- # Default value for <tt>:boost_mode</tt> might be changed
724
- # with <tt>Chewy.score_mode</tt> config option.
725
- def boost_mode value
726
- chain { criteria.update_options boost_mode: value }
727
- end
728
-
729
- # Sets the scoring mode for combining function scores/boosts
730
- # Not used if no score functions are specified.
731
- # Possible values:
732
- #
733
- # * <tt>:multiply</tt>
734
- # Default value. Scores are multiplied.
735
- #
736
- # Ex:
737
- #
738
- # UsersIndex.score_mode('multiply').script_score('doc['boost'].value')
739
- # # => {body: {query: function_score: {
740
- # query: {...},
741
- # score_mode: 'multiply',
742
- # functions: [ ... ]
743
- # }}}
744
- #
745
- # * <tt>:sum</tt>
746
- # Scores are summed.
747
- #
748
- # * <tt>:avg</tt>
749
- # Scores are averaged.
750
- #
751
- # * <tt>:first</tt>
752
- # The first function that has a matching filter is applied.
753
- #
754
- # * <tt>:max</tt>
755
- # Maximum score is used.
756
- #
757
- # * <tt>:min</tt>
758
- # Minimum score is used
759
- #
760
- # Default value for <tt>:score_mode</tt> might be changed
761
- # with <tt>Chewy.score_mode</tt> config option.
762
- #
763
- # Chewy.score_mode = :first
764
- #
765
- def score_mode value
766
- chain { criteria.update_options score_mode: value }
767
- end
768
-
769
- # Sets search request sorting
770
- #
771
- # UsersIndex.order(:first_name, :last_name).order(age: :desc).order(price: {order: :asc, mode: :avg})
772
- # # => {body: {
773
- # query: {...},
774
- # sort: ['first_name', 'last_name', {age: 'desc'}, {price: {order: 'asc', mode: 'avg'}}]
775
- # }}
776
- #
777
- def order *params
778
- chain { criteria.update_sort params }
779
- end
780
-
781
- # Cleans up previous search sorting and sets the new one
782
- #
783
- # UsersIndex.order(:first_name, :last_name).order(age: :desc).reorder(price: {order: :asc, mode: :avg})
784
- # # => {body: {
785
- # query: {...},
786
- # sort: [{price: {order: 'asc', mode: 'avg'}}]
787
- # }}
788
- #
789
- def reorder *params
790
- chain { criteria.update_sort params, purge: true }
791
- end
792
-
793
- # Sets search request field list
794
- #
795
- # UsersIndex.only(:first_name, :last_name).only(:age)
796
- # # => {body: {
797
- # query: {...},
798
- # fields: ['first_name', 'last_name', 'age']
799
- # }}
800
- #
801
- def only *params
802
- chain { criteria.update_fields params }
803
- end
804
-
805
- # Cleans up previous search field list and sets the new one
806
- #
807
- # UsersIndex.only(:first_name, :last_name).only!(:age)
808
- # # => {body: {
809
- # query: {...},
810
- # fields: ['age']
811
- # }}
812
- #
813
- def only! *params
814
- chain { criteria.update_fields params, purge: true }
815
- end
816
-
817
- # Specify types participating in the search result
818
- # Works via <tt>types</tt> filter. Always merged with another filters
819
- # with the <tt>and</tt> filter.
820
- #
821
- # UsersIndex.types(:admin, :manager).filters{ name == 'Johny' }.filters{ age <= 42 }
822
- # # => {body: {query: {filtered: {
823
- # query: {...},
824
- # filter: {and: [
825
- # {or: [
826
- # {type: {value: 'admin'}},
827
- # {type: {value: 'manager'}}
828
- # ]},
829
- # {term: {name: 'Johny'}},
830
- # {range: {age: {lte: 42}}}
831
- # ]}
832
- # }}}}
833
- #
834
- # UsersIndex.types(:admin, :manager).filters{ name == 'Johny' }.filters{ age <= 42 }.filter_mode(:or)
835
- # # => {body: {query: {filtered: {
836
- # query: {...},
837
- # filter: {and: [
838
- # {or: [
839
- # {type: {value: 'admin'}},
840
- # {type: {value: 'manager'}}
841
- # ]},
842
- # {or: [
843
- # {term: {name: 'Johny'}},
844
- # {range: {age: {lte: 42}}}
845
- # ]}
846
- # ]}
847
- # }}}}
848
- #
849
- def types *params
850
- chain { criteria.update_types params }
851
- end
852
-
853
- # Acts the same way as <tt>types</tt>, but cleans up previously set types
854
- #
855
- # UsersIndex.types(:admin).types!(:manager)
856
- # # => {body: {query: {filtered: {
857
- # query: {...},
858
- # filter: {type: {value: 'manager'}}
859
- # }}}}
860
- #
861
- def types! *params
862
- chain { criteria.update_types params, purge: true }
863
- end
864
-
865
- # Sets <tt>search_type</tt> for request.
866
- # For instance, one can use <tt>search_type=count</tt> to fetch only total count of records or to fetch only aggregations without fetching records.
867
- #
868
- # scope = UsersIndex.search_type(:count)
869
- # scope.count == 0 # no records actually fetched
870
- # scope.total == 10 # but we know a total count of them
871
- #
872
- # scope = UsersIndex.aggs(max_age: { max: { field: 'age' } }).search_type(:count)
873
- # max_age = scope.aggs['max_age']['value']
874
- #
875
- def search_type val
876
- chain { options.merge!(search_type: val) }
877
- end
878
-
879
- # Merges two queries.
880
- # Merges all the values in criteria with the same rules as values added manually.
881
- #
882
- # scope1 = UsersIndex.filter{ name == 'Johny' }
883
- # scope2 = UsersIndex.filter{ age <= 42 }
884
- # scope3 = UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }
885
- #
886
- # scope1.merge(scope2) == scope3 # => true
887
- #
888
- def merge other
889
- chain { criteria.merge!(other.criteria) }
890
- end
891
-
892
- # Deletes all records matching a query.
893
- #
894
- # UsersIndex.delete_all
895
- # UsersIndex.filter{ age <= 42 }.delete_all
896
- # UsersIndex::User.delete_all
897
- # UsersIndex::User.filter{ age <= 42 }.delete_all
898
- #
899
- def delete_all
900
- if Runtime.version > '2.0'
901
- plugins = Chewy.client.nodes.info(plugins: true)["nodes"].values.map { |item| item["plugins"] }.flatten
902
- raise PluginMissing, "install delete-by-query plugin" unless plugins.find { |item| item["name"] == 'delete-by-query' }
903
- end
904
- request = chain { criteria.update_options simple: true }.send(:_request)
905
- ActiveSupport::Notifications.instrument 'delete_query.chewy',
906
- request: request, indexes: _indexes, types: _types,
907
- index: _indexes.one? ? _indexes.first : _indexes,
908
- type: _types.one? ? _types.first : _types do
909
- Chewy.client.delete_by_query(request)
910
- end
911
- end
912
-
913
- # Find all records matching a query.
914
- #
915
- # UsersIndex.find(42)
916
- # UsersIndex.filter{ age <= 42 }.find(42)
917
- # UsersIndex::User.find(42)
918
- # UsersIndex::User.filter{ age <= 42 }.find(42)
919
- #
920
- # In all the previous examples find will return a single object.
921
- # To get a collection - pass an array of ids.
922
- #
923
- # UsersIndex::User.find(42, 7, 3) # array of objects with ids in [42, 7, 3]
924
- # UsersIndex::User.find([8, 13]) # array of objects with ids in [8, 13]
925
- # UsersIndex::User.find([42]) # array of the object with id == 42
926
- #
927
- def find *ids
928
- results = chain { criteria.update_options simple: true }.filter { _id == ids.flatten }.to_a
929
-
930
- raise Chewy::DocumentNotFound.new("Could not find documents for ids #{ids.flatten}") if results.empty?
931
- ids.one? && !ids.first.is_a?(Array) ? results.first : results
932
- end
933
-
934
- # Returns request total time elapsed as reported by elasticsearch
935
- #
936
- # UsersIndex.query(...).filter(...).took
937
- #
938
- def took
939
- _response['took']
940
- end
941
-
942
- # Returns request timed_out as reported by elasticsearch
943
- #
944
- # The timed_out value tells us whether the query timed out or not.
945
- #
946
- # By default, search requests do not timeout. If low response times are more
947
- # important to you than complete results, you can specify a timeout as 10 or
948
- # "10ms" (10 milliseconds), or "1s" (1 second). See #timeout method.
949
- #
950
- # UsersIndex.query(...).filter(...).timed_out
951
- #
952
- def timed_out
953
- _response['timed_out']
954
- end
955
-
956
- protected
957
-
958
- def initialize_clone other
959
- @criteria = other.criteria.clone
960
- reset
961
- end
962
-
963
- private
964
-
965
- def chain &block
966
- clone.tap { |q| q.instance_exec(&block) }
967
- end
968
-
969
- def reset
970
- @_request, @_response, @_results, @_collection = nil
971
- end
972
-
973
- def _request
974
- @_request ||= begin
975
- request = criteria.request_body
976
- request.merge!(index: _indexes.map(&:index_name), type: _types.map(&:type_name))
977
- request.merge!(search_type: options[:search_type]) if options[:search_type]
978
- request
979
- end
980
- end
981
-
982
- def _response
983
- @_response ||= ActiveSupport::Notifications.instrument 'search_query.chewy',
984
- request: _request, indexes: _indexes, types: _types,
985
- index: _indexes.one? ? _indexes.first : _indexes,
986
- type: _types.one? ? _types.first : _types do
987
- begin
988
- Chewy.client.search(_request)
989
- rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
990
- raise e if e.message !~ /IndexMissingException/ && e.message !~ /index_not_found_exception/
991
- {}
992
- end
993
- end
994
- end
995
-
996
- def _results
997
- @_results ||= (criteria.none? || _response == {} ? [] : _response['hits']['hits']).map do |hit|
998
- attributes = (hit['_source'] || {})
999
- .reverse_merge(id: hit['_id'])
1000
- .merge!(_score: hit['_score'])
1001
- .merge!(_explanation: hit['_explanation'])
1002
-
1003
- wrapper = _derive_index(hit['_index']).type_hash[hit['_type']].new attributes
1004
- wrapper._data = hit
1005
- wrapper
1006
- end
1007
- end
1008
-
1009
- def _collection
1010
- @_collection ||= begin
1011
- _load_objects! if criteria.options[:preload]
1012
- criteria.options[:preload] && criteria.options[:loaded_objects] ?
1013
- _results.map(&:_object) : _results
1014
- end
1015
- end
1016
-
1017
- def _derive_index index_name
1018
- (@derive_index ||= {})[index_name] ||= _indexes_hash[index_name] ||
1019
- _indexes_hash[_indexes_hash.keys.sort_by(&:length).reverse.detect { |name| index_name.start_with?(name) }]
1020
- end
1021
-
1022
- def _indexes_hash
1023
- @_indexes_hash ||= _indexes.index_by(&:index_name)
1024
- end
1025
- end
1026
- end