chewy 7.2.1 → 7.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/dependabot.yml +42 -0
  4. data/.github/workflows/ruby.yml +28 -26
  5. data/.rubocop.yml +4 -1
  6. data/CHANGELOG.md +196 -0
  7. data/Gemfile +4 -3
  8. data/README.md +203 -20
  9. data/chewy.gemspec +4 -18
  10. data/gemfiles/base.gemfile +12 -0
  11. data/gemfiles/rails.6.1.activerecord.gemfile +2 -1
  12. data/gemfiles/{rails.5.2.activerecord.gemfile → rails.7.0.activerecord.gemfile} +6 -3
  13. data/gemfiles/{rails.6.0.activerecord.gemfile → rails.7.1.activerecord.gemfile} +6 -3
  14. data/lib/chewy/config.rb +22 -14
  15. data/lib/chewy/elastic_client.rb +31 -0
  16. data/lib/chewy/errors.rb +11 -2
  17. data/lib/chewy/fields/base.rb +69 -13
  18. data/lib/chewy/fields/root.rb +2 -10
  19. data/lib/chewy/index/actions.rb +11 -16
  20. data/lib/chewy/index/adapter/active_record.rb +18 -3
  21. data/lib/chewy/index/adapter/object.rb +0 -10
  22. data/lib/chewy/index/adapter/orm.rb +4 -14
  23. data/lib/chewy/index/crutch.rb +15 -7
  24. data/lib/chewy/index/import/bulk_builder.rb +219 -32
  25. data/lib/chewy/index/import/bulk_request.rb +1 -1
  26. data/lib/chewy/index/import/routine.rb +3 -3
  27. data/lib/chewy/index/import.rb +45 -31
  28. data/lib/chewy/index/mapping.rb +2 -2
  29. data/lib/chewy/index/observe/active_record_methods.rb +87 -0
  30. data/lib/chewy/index/observe/callback.rb +34 -0
  31. data/lib/chewy/index/observe.rb +3 -58
  32. data/lib/chewy/index/syncer.rb +1 -1
  33. data/lib/chewy/index.rb +25 -0
  34. data/lib/chewy/journal.rb +17 -6
  35. data/lib/chewy/log_subscriber.rb +5 -1
  36. data/lib/chewy/minitest/helpers.rb +77 -0
  37. data/lib/chewy/minitest/search_index_receiver.rb +3 -1
  38. data/lib/chewy/rake_helper.rb +92 -11
  39. data/lib/chewy/rspec/build_query.rb +12 -0
  40. data/lib/chewy/rspec/helpers.rb +55 -0
  41. data/lib/chewy/rspec/update_index.rb +14 -7
  42. data/lib/chewy/rspec.rb +2 -0
  43. data/lib/chewy/runtime/version.rb +1 -1
  44. data/lib/chewy/runtime.rb +1 -1
  45. data/lib/chewy/search/parameters/collapse.rb +16 -0
  46. data/lib/chewy/search/parameters/ignore_unavailable.rb +27 -0
  47. data/lib/chewy/search/parameters/indices.rb +1 -1
  48. data/lib/chewy/search/parameters/knn.rb +16 -0
  49. data/lib/chewy/search/parameters/order.rb +6 -19
  50. data/lib/chewy/search/parameters/storage.rb +1 -1
  51. data/lib/chewy/search/parameters/track_total_hits.rb +16 -0
  52. data/lib/chewy/search/parameters.rb +4 -4
  53. data/lib/chewy/search/request.rb +74 -16
  54. data/lib/chewy/search/scoping.rb +1 -1
  55. data/lib/chewy/search.rb +5 -2
  56. data/lib/chewy/stash.rb +3 -3
  57. data/lib/chewy/strategy/active_job.rb +1 -1
  58. data/lib/chewy/strategy/atomic_no_refresh.rb +18 -0
  59. data/lib/chewy/strategy/base.rb +10 -0
  60. data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +168 -0
  61. data/lib/chewy/strategy/delayed_sidekiq/worker.rb +76 -0
  62. data/lib/chewy/strategy/delayed_sidekiq.rb +30 -0
  63. data/lib/chewy/strategy/lazy_sidekiq.rb +64 -0
  64. data/lib/chewy/strategy/sidekiq.rb +1 -1
  65. data/lib/chewy/strategy.rb +3 -0
  66. data/lib/chewy/version.rb +1 -1
  67. data/lib/chewy.rb +21 -14
  68. data/lib/tasks/chewy.rake +18 -2
  69. data/migration_guide.md +1 -1
  70. data/spec/chewy/config_spec.rb +2 -2
  71. data/spec/chewy/elastic_client_spec.rb +26 -0
  72. data/spec/chewy/fields/base_spec.rb +39 -18
  73. data/spec/chewy/index/actions_spec.rb +10 -10
  74. data/spec/chewy/index/adapter/active_record_spec.rb +88 -0
  75. data/spec/chewy/index/import/bulk_builder_spec.rb +309 -1
  76. data/spec/chewy/index/import/routine_spec.rb +5 -5
  77. data/spec/chewy/index/import_spec.rb +48 -26
  78. data/spec/chewy/index/observe/active_record_methods_spec.rb +68 -0
  79. data/spec/chewy/index/observe/callback_spec.rb +139 -0
  80. data/spec/chewy/index/observe_spec.rb +27 -0
  81. data/spec/chewy/journal_spec.rb +13 -49
  82. data/spec/chewy/minitest/helpers_spec.rb +111 -1
  83. data/spec/chewy/minitest/search_index_receiver_spec.rb +6 -4
  84. data/spec/chewy/rake_helper_spec.rb +170 -0
  85. data/spec/chewy/rspec/build_query_spec.rb +34 -0
  86. data/spec/chewy/rspec/helpers_spec.rb +61 -0
  87. data/spec/chewy/search/pagination/kaminari_examples.rb +1 -1
  88. data/spec/chewy/search/pagination/kaminari_spec.rb +1 -1
  89. data/spec/chewy/search/parameters/collapse_spec.rb +5 -0
  90. data/spec/chewy/search/parameters/ignore_unavailable_spec.rb +67 -0
  91. data/spec/chewy/search/parameters/knn_spec.rb +5 -0
  92. data/spec/chewy/search/parameters/order_spec.rb +18 -11
  93. data/spec/chewy/search/parameters/track_total_hits_spec.rb +5 -0
  94. data/spec/chewy/search/parameters_spec.rb +6 -1
  95. data/spec/chewy/search/request_spec.rb +58 -9
  96. data/spec/chewy/search_spec.rb +9 -0
  97. data/spec/chewy/strategy/active_job_spec.rb +8 -8
  98. data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
  99. data/spec/chewy/strategy/delayed_sidekiq_spec.rb +208 -0
  100. data/spec/chewy/strategy/lazy_sidekiq_spec.rb +214 -0
  101. data/spec/chewy/strategy/sidekiq_spec.rb +4 -4
  102. data/spec/chewy_spec.rb +10 -7
  103. data/spec/spec_helper.rb +1 -2
  104. data/spec/support/active_record.rb +8 -1
  105. metadata +45 -264
  106. data/lib/chewy/backports/deep_dup.rb +0 -46
  107. data/lib/chewy/backports/duplicable.rb +0 -91
  108. data/lib/chewy/index/import/thread_safe_progress_bar.rb +0 -40
@@ -19,6 +19,9 @@ module Chewy
19
19
  output.puts " Applying journal to #{targets}, #{count} entries, stage #{payload[:stage]}"
20
20
  end
21
21
 
22
+ DELETE_BY_QUERY_OPTIONS = %w[WAIT_FOR_COMPLETION REQUESTS_PER_SECOND SCROLL_SIZE].freeze
23
+ FALSE_VALUES = %w[0 f false off].freeze
24
+
22
25
  class << self
23
26
  # Performs zero-downtime reindexing of all documents for the specified indexes
24
27
  #
@@ -35,6 +38,8 @@ module Chewy
35
38
  # @param output [IO] output io for logging
36
39
  # @return [Array<Chewy::Index>] indexes that were reset
37
40
  def reset(only: nil, except: nil, parallel: nil, output: $stdout)
41
+ warn_missing_index(output)
42
+
38
43
  subscribed_task_stats(output) do
39
44
  indexes_from(only: only, except: except).each do |index|
40
45
  reset_one(index, output, parallel: parallel)
@@ -58,6 +63,8 @@ module Chewy
58
63
  # @param output [IO] output io for logging
59
64
  # @return [Array<Chewy::Index>] indexes that were actually reset
60
65
  def upgrade(only: nil, except: nil, parallel: nil, output: $stdout)
66
+ warn_missing_index(output)
67
+
61
68
  subscribed_task_stats(output) do
62
69
  indexes = indexes_from(only: only, except: except)
63
70
 
@@ -158,7 +165,7 @@ module Chewy
158
165
 
159
166
  subscribed_task_stats(output) do
160
167
  output.puts "Applying journal entries created after #{time}"
161
- count = Chewy::Journal.new(indexes_from(only: only, except: except)).apply(time)
168
+ count = Chewy::Journal.new(journal_indexes_from(only: only, except: except)).apply(time)
162
169
  output.puts 'No journal entries were created after the specified time' if count.zero?
163
170
  end
164
171
  end
@@ -177,12 +184,29 @@ module Chewy
177
184
  # @param except [Array<Chewy::Index, String>, Chewy::Index, String] indexes to exclude from processing
178
185
  # @param output [IO] output io for logging
179
186
  # @return [Array<Chewy::Index>] indexes that were actually updated
180
- def journal_clean(time: nil, only: nil, except: nil, output: $stdout)
187
+ def journal_clean(time: nil, only: nil, except: nil, delete_by_query_options: {}, output: $stdout)
181
188
  subscribed_task_stats(output) do
182
189
  output.puts "Cleaning journal entries created before #{time}" if time
183
- response = Chewy::Journal.new(indexes_from(only: only, except: except)).clean(time)
184
- count = response['deleted'] || response['_indices']['_all']['deleted']
185
- output.puts "Cleaned up #{count} journal entries"
190
+ response = Chewy::Journal.new(journal_indexes_from(only: only, except: except)).clean(time, delete_by_query_options: delete_by_query_options)
191
+ if response.key?('task')
192
+ output.puts "Task to cleanup the journal has been created, #{response['task']}"
193
+ else
194
+ count = response['deleted'] || response['_indices']['_all']['deleted']
195
+ output.puts "Cleaned up #{count} journal entries"
196
+ end
197
+ end
198
+ end
199
+
200
+ # Creates journal index.
201
+ #
202
+ # @example
203
+ # Chewy::RakeHelper.journal_create # creates journal
204
+ #
205
+ # @param output [IO] output io for logging
206
+ # @return Chewy::Index Returns instance of chewy index
207
+ def journal_create(output: $stdout)
208
+ subscribed_task_stats(output) do
209
+ Chewy::Stash::Journal.create!
186
210
  end
187
211
  end
188
212
 
@@ -224,6 +248,44 @@ module Chewy
224
248
  end
225
249
  end
226
250
 
251
+ # Reads options that are required to run journal cleanup asynchronously from ENV hash
252
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
253
+ #
254
+ # @example
255
+ # Chewy::RakeHelper.delete_by_query_options_from_env({'WAIT_FOR_COMPLETION' => 'false','REQUESTS_PER_SECOND' => '10','SCROLL_SIZE' => '5000'})
256
+ # # => { wait_for_completion: false, requests_per_second: 10.0, scroll_size: 5000 }
257
+ #
258
+ def delete_by_query_options_from_env(env)
259
+ env
260
+ .slice(*DELETE_BY_QUERY_OPTIONS)
261
+ .transform_keys { |k| k.downcase.to_sym }
262
+ .to_h do |key, value|
263
+ case key
264
+ when :wait_for_completion then [key, !FALSE_VALUES.include?(value.downcase)]
265
+ when :requests_per_second then [key, value.to_f]
266
+ when :scroll_size then [key, value.to_i]
267
+ end
268
+ end
269
+ end
270
+
271
+ def create_missing_indexes!(output: $stdout, env: ENV)
272
+ subscribed_task_stats(output) do
273
+ Chewy.eager_load!
274
+ all_indexes = Chewy::Index.descendants
275
+ all_indexes -= [Chewy::Stash::Journal] unless Chewy.configuration[:journal]
276
+ all_indexes.each do |index|
277
+ if index.exists?
278
+ output.puts "#{index.name} already exists, skipping" if env['VERBOSE']
279
+ next
280
+ end
281
+
282
+ index.create!
283
+
284
+ output.puts "#{index.name} index successfully created"
285
+ end
286
+ end
287
+ end
288
+
227
289
  def normalize_indexes(*identifiers)
228
290
  identifiers.flatten(1).map { |identifier| normalize_index(identifier) }
229
291
  end
@@ -239,11 +301,18 @@ module Chewy
239
301
  ActiveSupport::Notifications.subscribed(JOURNAL_CALLBACK.curry[output], 'apply_journal.chewy') do
240
302
  ActiveSupport::Notifications.subscribed(IMPORT_CALLBACK.curry[output], 'import_objects.chewy', &block)
241
303
  end
304
+ ensure
242
305
  output.puts "Total: #{human_duration(Time.now - start)}"
243
306
  end
244
307
 
245
308
  private
246
309
 
310
+ def journal_indexes_from(only: nil, except: nil)
311
+ return if Array.wrap(only).empty? && Array.wrap(except).empty?
312
+
313
+ indexes_from(only: only, except: except)
314
+ end
315
+
247
316
  def indexes_from(only: nil, except: nil)
248
317
  indexes = if only.present?
249
318
  normalize_indexes(Array.wrap(only))
@@ -251,11 +320,7 @@ module Chewy
251
320
  all_indexes
252
321
  end
253
322
 
254
- indexes = if except.present?
255
- indexes - normalize_indexes(Array.wrap(except))
256
- else
257
- indexes
258
- end
323
+ indexes -= normalize_indexes(Array.wrap(except)) if except.present?
259
324
 
260
325
  indexes.sort_by(&:derivable_name)
261
326
  end
@@ -271,7 +336,23 @@ module Chewy
271
336
 
272
337
  def reset_one(index, output, parallel: false)
273
338
  output.puts "Resetting #{index}"
274
- index.reset!((Time.now.to_f * 1000).round, parallel: parallel, progressbar: ENV['PROGRESS'] == 'true')
339
+ index.reset!((Time.now.to_f * 1000).round, parallel: parallel, apply_journal: journal_exists?)
340
+ end
341
+
342
+ def warn_missing_index(output)
343
+ return if journal_exists?
344
+
345
+ output.puts "############################################################\n" \
346
+ "WARN: You are risking to lose some changes during the reset.\n " \
347
+ "Please consider enabling journaling.\n " \
348
+ "See https://github.com/toptal/chewy#journaling\n" \
349
+ '############################################################'
350
+ end
351
+
352
+ def journal_exists?
353
+ @journal_exists = Chewy::Stash::Journal.exists? if @journal_exists.nil?
354
+
355
+ @journal_exists
275
356
  end
276
357
  end
277
358
  end
@@ -0,0 +1,12 @@
1
+ # Rspec helper to compare request and expected query
2
+ # To use it - add `require 'chewy/rspec/build_query'` to the `spec_helper.rb`
3
+ # Simple usage - just pass expected response as argument
4
+ # and then call needed query.
5
+ #
6
+ # expect { method1.method2...methodN }.to build_query(expected_query)
7
+ #
8
+ RSpec::Matchers.define :build_query do |expected_query = {}|
9
+ match do |request|
10
+ request.render == expected_query
11
+ end
12
+ end
@@ -0,0 +1,55 @@
1
+ module Chewy
2
+ module Rspec
3
+ module Helpers
4
+ extend ActiveSupport::Concern
5
+ # Rspec helper to mock elasticsearch response
6
+ # To use it - add `require 'chewy/rspec'` to the `spec_helper.rb`
7
+ #
8
+ # mock_elasticsearch_response(CitiesIndex, raw_response)
9
+ # expect(CitiesIndex.query({}).hits).to eq(hits)
10
+ #
11
+ def mock_elasticsearch_response(index, raw_response)
12
+ mocked_request = Chewy::Search::Request.new(index)
13
+ allow(Chewy::Search::Request).to receive(:new).and_return(mocked_request)
14
+ allow(mocked_request).to receive(:perform).and_return(raw_response)
15
+ end
16
+
17
+ # Rspec helper to mock Elasticsearch response source
18
+ # To use it - add `require 'chewy/rspec'` to the `spec_helper.rb`
19
+ #
20
+ # mock_elasticsearch_response_sources(CitiesIndex, sources)
21
+ # expect(CitiesIndex.query({}).hits).to eq(hits)
22
+ #
23
+ def mock_elasticsearch_response_sources(index, hits)
24
+ raw_response = {
25
+ 'took' => 4,
26
+ 'timed_out' => false,
27
+ '_shards' => {
28
+ 'total' => 1,
29
+ 'successful' => 1,
30
+ 'skipped' => 0,
31
+ 'failed' => 0
32
+ },
33
+ 'hits' => {
34
+ 'total' => {
35
+ 'value' => hits.count,
36
+ 'relation' => 'eq'
37
+ },
38
+ 'max_score' => 1.0,
39
+ 'hits' => hits.each_with_index.map do |hit, i|
40
+ {
41
+ '_index' => index.index_name,
42
+ '_type' => '_doc',
43
+ '_id' => (i + 1).to_s,
44
+ '_score' => 3.14,
45
+ '_source' => hit
46
+ }
47
+ end
48
+ }
49
+ }
50
+
51
+ mock_elasticsearch_response(index, raw_response)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,4 +1,4 @@
1
- require 'i18n/core_ext/hash'
1
+ require 'active_support/core_ext/hash/keys'
2
2
 
3
3
  # Rspec matcher `update_index`
4
4
  # To use it - add `require 'chewy/rspec'` to the `spec_helper.rb`
@@ -88,6 +88,11 @@ RSpec::Matchers.define :update_index do |index_name, options = {}| # rubocop:dis
88
88
  @only = true
89
89
  end
90
90
 
91
+ # Expect import to be called with refresh=false parameter
92
+ chain(:no_refresh) do
93
+ @no_refresh = true
94
+ end
95
+
91
96
  def supports_block_expectations?
92
97
  true
93
98
  end
@@ -100,11 +105,13 @@ RSpec::Matchers.define :update_index do |index_name, options = {}| # rubocop:dis
100
105
 
101
106
  index = Chewy.derive_name(index_name)
102
107
  if defined?(Mocha) && RSpec.configuration.mock_framework.to_s == 'RSpec::Core::MockingAdapters::Mocha'
103
- Chewy::Index::Import::BulkRequest.stubs(:new).with(index, any_parameters).returns(mock_bulk_request)
108
+ params_matcher = @no_refresh ? has_entry(refresh: false) : any_parameters
109
+ Chewy::Index::Import::BulkRequest.stubs(:new).with(index, params_matcher).returns(mock_bulk_request)
104
110
  else
105
- mocked_already = ::RSpec::Mocks.space.proxy_for(Chewy::Index::Import::BulkRequest).method_double_if_exists_for_message(:new)
111
+ mocked_already = RSpec::Mocks.space.proxy_for(Chewy::Index::Import::BulkRequest).method_double_if_exists_for_message(:new)
106
112
  allow(Chewy::Index::Import::BulkRequest).to receive(:new).and_call_original unless mocked_already
107
- allow(Chewy::Index::Import::BulkRequest).to receive(:new).with(index, any_args).and_return(mock_bulk_request)
113
+ params_matcher = @no_refresh ? hash_including(refresh: false) : any_args
114
+ allow(Chewy::Index::Import::BulkRequest).to receive(:new).with(index, params_matcher).and_return(mock_bulk_request)
108
115
  end
109
116
 
110
117
  Chewy.strategy(options[:strategy] || :atomic) { block.call }
@@ -146,7 +153,7 @@ RSpec::Matchers.define :update_index do |index_name, options = {}| # rubocop:dis
146
153
  output = ''
147
154
 
148
155
  if mock_bulk_request.updates.none?
149
- output << "Expected index `#{index_name}` to be updated, but it was not\n"
156
+ output << "Expected index `#{index_name}` to be updated#{' with no refresh' if @no_refresh}, but it was not\n"
150
157
  elsif @missed_reindex.present? || @missed_delete.present?
151
158
  message = "Expected index `#{index_name}` "
152
159
  message << [
@@ -213,7 +220,7 @@ RSpec::Matchers.define :update_index do |index_name, options = {}| # rubocop:dis
213
220
  expected_count = options[:times] || options[:count]
214
221
  expected_attributes = (options[:with] || options[:attributes] || {}).deep_symbolize_keys
215
222
 
216
- args.flatten.map do |document|
223
+ args.flatten.to_h do |document|
217
224
  id = document.respond_to?(:id) ? document.id.to_s : document.to_s
218
225
  [id, {
219
226
  document: document,
@@ -222,7 +229,7 @@ RSpec::Matchers.define :update_index do |index_name, options = {}| # rubocop:dis
222
229
  real_count: 0,
223
230
  real_attributes: {}
224
231
  }]
225
- end.to_h
232
+ end
226
233
  end
227
234
 
228
235
  def compare_attributes(expected, real)
data/lib/chewy/rspec.rb CHANGED
@@ -1 +1,3 @@
1
+ require 'chewy/rspec/build_query'
2
+ require 'chewy/rspec/helpers'
1
3
  require 'chewy/rspec/update_index'
@@ -5,7 +5,7 @@ module Chewy
5
5
  attr_reader :major, :minor, :patch
6
6
 
7
7
  def initialize(version)
8
- @major, @minor, @patch = *(version.to_s.split('.', 3) + [0] * 3).first(3).map(&:to_i)
8
+ @major, @minor, @patch = *(version.to_s.split('.', 3) + ([0] * 3)).first(3).map(&:to_i)
9
9
  end
10
10
 
11
11
  def to_s
data/lib/chewy/runtime.rb CHANGED
@@ -3,7 +3,7 @@ require 'chewy/runtime/version'
3
3
  module Chewy
4
4
  module Runtime
5
5
  def self.version
6
- Thread.current[:chewy_runtime_version] ||= Version.new(Chewy.client.info['version']['number'])
6
+ Chewy.current[:chewy_runtime_version] ||= Version.new(Chewy.client.info['version']['number'])
7
7
  end
8
8
  end
9
9
  end
@@ -0,0 +1,16 @@
1
+ require 'chewy/search/parameters/storage'
2
+
3
+ module Chewy
4
+ module Search
5
+ class Parameters
6
+ # Just a standard hash storage. Nothing to see here.
7
+ #
8
+ # @see Chewy::Search::Parameters::HashStorage
9
+ # @see Chewy::Search::Request#collapse
10
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/collapse-search-results.html
11
+ class Collapse < Storage
12
+ include HashStorage
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ require 'chewy/search/parameters/storage'
2
+
3
+ module Chewy
4
+ module Search
5
+ class Parameters
6
+ # Stores boolean value, but has 3 states: `true`, `false` and `nil`.
7
+ #
8
+ # @see Chewy::Search::Request#ignore_unavailable
9
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-index.html#multi-index
10
+ class IgnoreUnavailable < Storage
11
+ # We don't want to render `nil`, but render `true` and `false` values.
12
+ #
13
+ # @see Chewy::Search::Parameters::Storage#render
14
+ # @return [{Symbol => Object}, nil]
15
+ def render
16
+ {self.class.param_name => value} unless value.nil?
17
+ end
18
+
19
+ private
20
+
21
+ def normalize(value)
22
+ !!value unless value.nil?
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -17,7 +17,7 @@ module Chewy
17
17
  # @param other [Chewy::Search::Parameters::Storage] any storage instance
18
18
  # @return [true, false] the result of comparison
19
19
  def ==(other)
20
- super || other.class == self.class && other.render == render
20
+ super || (other.class == self.class && other.render == render)
21
21
  end
22
22
 
23
23
  # Just adds indices to indices.
@@ -0,0 +1,16 @@
1
+ require 'chewy/search/parameters/storage'
2
+
3
+ module Chewy
4
+ module Search
5
+ class Parameters
6
+ # Just a standard hash storage. Nothing to see here.
7
+ #
8
+ # @see Chewy::Search::Parameters::HashStorage
9
+ # @see Chewy::Search::Request#knn
10
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html
11
+ class Knn < Storage
12
+ include HashStorage
13
+ end
14
+ end
15
+ end
16
+ end
@@ -17,7 +17,7 @@ module Chewy
17
17
  # @param other_value [Object] any acceptable storage value
18
18
  # @return [Object] updated value
19
19
  def update!(other_value)
20
- value.merge!(normalize(other_value))
20
+ value.concat(normalize(other_value))
21
21
  end
22
22
 
23
23
  # Size requires specialized rendering logic, it should return
@@ -28,20 +28,7 @@ module Chewy
28
28
  def render
29
29
  return if value.blank?
30
30
 
31
- sort = value.map do |(field, options)|
32
- options ? {field => options} : field
33
- end
34
- {sort: sort}
35
- end
36
-
37
- # Comparison also reqires additional logic. Hashes are compared
38
- # orderlessly, but for `sort` parameter oder is important, so we
39
- # compare hash key collections additionally.
40
- #
41
- # @see Chewy::Search::Parameters::Storage#==
42
- # @return [true, false]
43
- def ==(other)
44
- super && value.keys == other.value.keys
31
+ {sort: value}
45
32
  end
46
33
 
47
34
  private
@@ -49,13 +36,13 @@ module Chewy
49
36
  def normalize(value)
50
37
  case value
51
38
  when Array
52
- value.each_with_object({}) do |sv, res|
53
- res.merge!(normalize(sv))
39
+ value.each_with_object([]) do |sv, res|
40
+ res.concat(normalize(sv))
54
41
  end
55
42
  when Hash
56
- value.stringify_keys
43
+ [value.stringify_keys]
57
44
  else
58
- value.present? ? {value.to_s => nil} : {}
45
+ value.present? ? [value.to_s] : []
59
46
  end
60
47
  end
61
48
  end
@@ -35,7 +35,7 @@ module Chewy
35
35
  # @param other [Chewy::Search::Parameters::Storage] any storage instance
36
36
  # @return [true, false] the result of comparision
37
37
  def ==(other)
38
- super || other.class == self.class && other.value == value
38
+ super || (other.class == self.class && other.value == value)
39
39
  end
40
40
 
41
41
  # Replaces current value with normalized provided one. Doesn't
@@ -0,0 +1,16 @@
1
+ require 'chewy/search/parameters/storage'
2
+
3
+ module Chewy
4
+ module Search
5
+ class Parameters
6
+ # Just a standard boolean storage, nothing to see here.
7
+ #
8
+ # @see Chewy::Search::Parameters::BoolStorage
9
+ # @see Chewy::Search::Request#track_total_hits
10
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-your-data.html#track-total-hits
11
+ class TrackTotalHits < Storage
12
+ include BoolStorage
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
- Dir.glob(File.join(File.dirname(__FILE__), 'parameters', 'concerns', '*.rb')).sort.each { |f| require f }
2
- Dir.glob(File.join(File.dirname(__FILE__), 'parameters', '*.rb')).sort.each { |f| require f }
1
+ Dir.glob(File.join(File.dirname(__FILE__), 'parameters', 'concerns', '*.rb')).each { |f| require f }
2
+ Dir.glob(File.join(File.dirname(__FILE__), 'parameters', '*.rb')).each { |f| require f }
3
3
 
4
4
  module Chewy
5
5
  module Search
@@ -10,7 +10,7 @@ module Chewy
10
10
  # @see Chewy::Search::Request#parameters
11
11
  # @see Chewy::Search::Parameters::Storage
12
12
  class Parameters
13
- QUERY_STRING_STORAGES = %i[indices search_type request_cache allow_partial_search_results].freeze
13
+ QUERY_STRING_STORAGES = %i[indices preference search_type request_cache allow_partial_search_results ignore_unavailable].freeze
14
14
 
15
15
  # Default storage classes warehouse. It is probably possible to
16
16
  # add your own classes here if necessary, but I'm not sure it will work.
@@ -53,7 +53,7 @@ module Chewy
53
53
  # @param other [Object] any object
54
54
  # @return [true, false]
55
55
  def ==(other)
56
- super || other.is_a?(self.class) && compare_storages(other)
56
+ super || (other.is_a?(self.class) && compare_storages(other))
57
57
  end
58
58
 
59
59
  # Clones the specified storage, performs the operation
@@ -18,17 +18,17 @@ module Chewy
18
18
  include Scoping
19
19
  include Scrolling
20
20
  UNDEFINED = Class.new.freeze
21
- EVERFIELDS = %w[_index _type _id _parent].freeze
21
+ EVERFIELDS = %w[_index _type _id _parent _routing].freeze
22
22
  DELEGATED_METHODS = %i[
23
- query filter post_filter order reorder docvalue_fields
24
- track_scores request_cache explain version profile
23
+ query filter post_filter knn order reorder docvalue_fields
24
+ track_scores track_total_hits request_cache explain version profile
25
25
  search_type preference limit offset terminate_after
26
26
  timeout min_score source stored_fields search_after
27
- load script_fields suggest aggs aggregations none
27
+ load script_fields suggest aggs aggregations collapse none
28
28
  indices_boost rescore highlight total total_count
29
29
  total_entries indices types delete_all count exists?
30
30
  exist? find pluck scroll_batches scroll_hits
31
- scroll_results scroll_wrappers
31
+ scroll_results scroll_wrappers ignore_unavailable
32
32
  ].to_set.freeze
33
33
  DEFAULT_BATCH_SIZE = 1000
34
34
  DEFAULT_PLUCK_BATCH_SIZE = 10_000
@@ -41,7 +41,7 @@ module Chewy
41
41
  EXTRA_STORAGES = %i[aggs suggest].freeze
42
42
  # An array of storage names that are changing the returned hist collection in any way.
43
43
  WHERE_STORAGES = %i[
44
- query filter post_filter none min_score rescore indices_boost
44
+ query filter post_filter knn none min_score rescore indices_boost collapse
45
45
  ].freeze
46
46
 
47
47
  delegate :hits, :wrappers, :objects, :records, :documents,
@@ -336,6 +336,19 @@ module Chewy
336
336
  # @param value [true, false]
337
337
  # @return [Chewy::Search::Request]
338
338
  #
339
+ # @!method track_total_hits(value = true)
340
+ # Replaces the value of the `track_total_hits` parameter with the provided value.
341
+ #
342
+ # @example
343
+ # PlacesIndex.track_total_hits
344
+ # # => <PlacesIndex::Query {..., :body=>{:track_total_hits=>true}}>
345
+ # PlacesIndex.track_total_hits.track_total_hits(false)
346
+ # # => <PlacesIndex::Query {:index=>["places"]}>
347
+ # @see Chewy::Search::Parameters::TrackTotalHits
348
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-your-data.html#track-total-hits
349
+ # @param value [true, false]
350
+ # @return [Chewy::Search::Request]
351
+ #
339
352
  # @!method explain(value = true)
340
353
  # Replaces the value of the `explain` parameter with the provided value.
341
354
  #
@@ -388,7 +401,7 @@ module Chewy
388
401
  # @see https://en.wikipedia.org/wiki/Null_Object_pattern
389
402
  # @param value [true, false]
390
403
  # @return [Chewy::Search::Request]
391
- %i[track_scores explain version profile none].each do |name|
404
+ %i[track_scores track_total_hits explain version profile none].each do |name|
392
405
  define_method name do |value = true|
393
406
  modify(name) { replace!(value) }
394
407
  end
@@ -485,7 +498,40 @@ module Chewy
485
498
  # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html#search-api-min-score
486
499
  # @param value [String, Integer, Float]
487
500
  # @return [Chewy::Search::Request]
488
- %i[request_cache search_type preference timeout limit offset terminate_after min_score].each do |name|
501
+ #
502
+ # @!method ignore_unavailable(value)
503
+ # Replaces the value of the `ignore_unavailable` request part.
504
+ #
505
+ # @example
506
+ # PlacesIndex.ignore_unavailable(true)
507
+ # <PlacesIndex::Query {..., :ignore_unavailable => true, :body=>{ ... }}>
508
+ # @see Chewy::Search::Parameters::IgnoreUnavailable
509
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-index.html#multi-index
510
+ # @param value [true, false, nil]
511
+ # @return [Chewy::Search::Request]
512
+ #
513
+ # @!method collapse(value)
514
+ # Replaces the value of the `collapse` request part.
515
+ #
516
+ # @example
517
+ # PlacesIndex.collapse(field: :name)
518
+ # # => <PlacesIndex::Query {..., :body=>{:collapse=>{"field"=>:name}}}>
519
+ # @see Chewy::Search::Parameters::Collapse
520
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/collapse-search-results.html
521
+ # @param value [Hash]
522
+ # @return [Chewy::Search::Request]
523
+ #
524
+ # @!method knn(value)
525
+ # Replaces the value of the `knn` request part.
526
+ #
527
+ # @example
528
+ # PlacesIndex.knn(field: :vector, query_vector: [4, 2], k: 5, num_candidates: 50)
529
+ # # => <PlacesIndex::Query {..., :body=>{:knn=>{"field"=>:vector, "query_vector"=>[4, 2], "k"=>5, "num_candidates"=>50}}}>
530
+ # @see Chewy::Search::Parameters::Knn
531
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html
532
+ # @param value [Hash]
533
+ # @return [Chewy::Search::Request]
534
+ %i[request_cache search_type preference timeout limit offset terminate_after min_score ignore_unavailable collapse knn].each do |name|
489
535
  define_method name do |value|
490
536
  modify(name) { replace!(value) }
491
537
  end
@@ -776,8 +822,8 @@ module Chewy
776
822
  # Returns a new scope containing only specified storages.
777
823
  #
778
824
  # @example
779
- # PlacesIndex.limit(10).offset(10).order(:name).except(:offset, :order)
780
- # # => <PlacesIndex::Query {..., :body=>{:size=>10}}>
825
+ # PlacesIndex.limit(10).offset(10).order(:name).only(:offset, :order)
826
+ # # => <PlacesIndex::Query {..., :body=>{:from=>10, :sort=>["name"]}}>
781
827
  # @param values [Array<String, Symbol>]
782
828
  # @return [Chewy::Search::Request] new scope
783
829
  def only(*values)
@@ -787,8 +833,8 @@ module Chewy
787
833
  # Returns a new scope containing all the storages except specified.
788
834
  #
789
835
  # @example
790
- # PlacesIndex.limit(10).offset(10).order(:name).only(:offset, :order)
791
- # # => <PlacesIndex::Query {..., :body=>{:from=>10, :sort=>["name"]}}>
836
+ # PlacesIndex.limit(10).offset(10).order(:name).except(:offset, :order)
837
+ # # => <PlacesIndex::Query {..., :body=>{:size=>10}}>
792
838
  # @param values [Array<String, Symbol>]
793
839
  # @return [Chewy::Search::Request] new scope
794
840
  def except(*values)
@@ -890,7 +936,7 @@ module Chewy
890
936
 
891
937
  # Returns and array of values for specified fields.
892
938
  # Uses `source` to restrict the list of returned fields.
893
- # Fields `_id`, `_type` and `_index` are also supported.
939
+ # Fields `_id`, `_type`, `_routing` and `_index` are also supported.
894
940
  #
895
941
  # @overload pluck(field)
896
942
  # If single field is passed - it returns and array of values.
@@ -927,10 +973,22 @@ module Chewy
927
973
  #
928
974
  # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
929
975
  # @note The result hash is different for different API used.
930
- # @param refresh [true, false] field names
976
+ # @param refresh [true, false] Refreshes all shards involved in the delete by query
977
+ # @param wait_for_completion [true, false] wait for request completion or run it asynchronously
978
+ # and return task reference at `.tasks/task/${taskId}`.
979
+ # @param requests_per_second [Float] The throttle for this request in sub-requests per second
980
+ # @param scroll_size [Integer] Size of the scroll request that powers the operation
981
+
931
982
  # @return [Hash] the result of query execution
932
- def delete_all(refresh: true)
933
- request_body = only(WHERE_STORAGES).render.merge(refresh: refresh)
983
+ def delete_all(refresh: true, wait_for_completion: nil, requests_per_second: nil, scroll_size: nil)
984
+ request_body = only(WHERE_STORAGES).render.merge(
985
+ {
986
+ refresh: refresh,
987
+ wait_for_completion: wait_for_completion,
988
+ requests_per_second: requests_per_second,
989
+ scroll_size: scroll_size
990
+ }.compact
991
+ )
934
992
  ActiveSupport::Notifications.instrument 'delete_query.chewy', notification_payload(request: request_body) do
935
993
  request_body[:body] = {query: {match_all: {}}} if request_body[:body].empty?
936
994
  Chewy.client.delete_by_query(request_body)
@@ -29,7 +29,7 @@ module Chewy
29
29
  #
30
30
  # @return [Array<Chewy::Search::Request>] array of scopes
31
31
  def scopes
32
- Thread.current[:chewy_scopes] ||= []
32
+ Chewy.current[:chewy_scopes] ||= []
33
33
  end
34
34
  end
35
35