chewy 7.2.1 → 7.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/.github/dependabot.yml +42 -0
- data/.github/workflows/ruby.yml +28 -26
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +196 -0
- data/Gemfile +4 -3
- data/README.md +203 -20
- data/chewy.gemspec +4 -18
- data/gemfiles/base.gemfile +12 -0
- data/gemfiles/rails.6.1.activerecord.gemfile +2 -1
- data/gemfiles/{rails.5.2.activerecord.gemfile → rails.7.0.activerecord.gemfile} +6 -3
- data/gemfiles/{rails.6.0.activerecord.gemfile → rails.7.1.activerecord.gemfile} +6 -3
- data/lib/chewy/config.rb +22 -14
- data/lib/chewy/elastic_client.rb +31 -0
- data/lib/chewy/errors.rb +11 -2
- data/lib/chewy/fields/base.rb +69 -13
- data/lib/chewy/fields/root.rb +2 -10
- data/lib/chewy/index/actions.rb +11 -16
- data/lib/chewy/index/adapter/active_record.rb +18 -3
- data/lib/chewy/index/adapter/object.rb +0 -10
- data/lib/chewy/index/adapter/orm.rb +4 -14
- data/lib/chewy/index/crutch.rb +15 -7
- data/lib/chewy/index/import/bulk_builder.rb +219 -32
- data/lib/chewy/index/import/bulk_request.rb +1 -1
- data/lib/chewy/index/import/routine.rb +3 -3
- data/lib/chewy/index/import.rb +45 -31
- data/lib/chewy/index/mapping.rb +2 -2
- data/lib/chewy/index/observe/active_record_methods.rb +87 -0
- data/lib/chewy/index/observe/callback.rb +34 -0
- data/lib/chewy/index/observe.rb +3 -58
- data/lib/chewy/index/syncer.rb +1 -1
- data/lib/chewy/index.rb +25 -0
- data/lib/chewy/journal.rb +17 -6
- data/lib/chewy/log_subscriber.rb +5 -1
- data/lib/chewy/minitest/helpers.rb +77 -0
- data/lib/chewy/minitest/search_index_receiver.rb +3 -1
- data/lib/chewy/rake_helper.rb +92 -11
- data/lib/chewy/rspec/build_query.rb +12 -0
- data/lib/chewy/rspec/helpers.rb +55 -0
- data/lib/chewy/rspec/update_index.rb +14 -7
- data/lib/chewy/rspec.rb +2 -0
- data/lib/chewy/runtime/version.rb +1 -1
- data/lib/chewy/runtime.rb +1 -1
- data/lib/chewy/search/parameters/collapse.rb +16 -0
- data/lib/chewy/search/parameters/ignore_unavailable.rb +27 -0
- data/lib/chewy/search/parameters/indices.rb +1 -1
- data/lib/chewy/search/parameters/knn.rb +16 -0
- data/lib/chewy/search/parameters/order.rb +6 -19
- data/lib/chewy/search/parameters/storage.rb +1 -1
- data/lib/chewy/search/parameters/track_total_hits.rb +16 -0
- data/lib/chewy/search/parameters.rb +4 -4
- data/lib/chewy/search/request.rb +74 -16
- data/lib/chewy/search/scoping.rb +1 -1
- data/lib/chewy/search.rb +5 -2
- data/lib/chewy/stash.rb +3 -3
- data/lib/chewy/strategy/active_job.rb +1 -1
- data/lib/chewy/strategy/atomic_no_refresh.rb +18 -0
- data/lib/chewy/strategy/base.rb +10 -0
- data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +168 -0
- data/lib/chewy/strategy/delayed_sidekiq/worker.rb +76 -0
- data/lib/chewy/strategy/delayed_sidekiq.rb +30 -0
- data/lib/chewy/strategy/lazy_sidekiq.rb +64 -0
- data/lib/chewy/strategy/sidekiq.rb +1 -1
- data/lib/chewy/strategy.rb +3 -0
- data/lib/chewy/version.rb +1 -1
- data/lib/chewy.rb +21 -14
- data/lib/tasks/chewy.rake +18 -2
- data/migration_guide.md +1 -1
- data/spec/chewy/config_spec.rb +2 -2
- data/spec/chewy/elastic_client_spec.rb +26 -0
- data/spec/chewy/fields/base_spec.rb +39 -18
- data/spec/chewy/index/actions_spec.rb +10 -10
- data/spec/chewy/index/adapter/active_record_spec.rb +88 -0
- data/spec/chewy/index/import/bulk_builder_spec.rb +309 -1
- data/spec/chewy/index/import/routine_spec.rb +5 -5
- data/spec/chewy/index/import_spec.rb +48 -26
- data/spec/chewy/index/observe/active_record_methods_spec.rb +68 -0
- data/spec/chewy/index/observe/callback_spec.rb +139 -0
- data/spec/chewy/index/observe_spec.rb +27 -0
- data/spec/chewy/journal_spec.rb +13 -49
- data/spec/chewy/minitest/helpers_spec.rb +111 -1
- data/spec/chewy/minitest/search_index_receiver_spec.rb +6 -4
- data/spec/chewy/rake_helper_spec.rb +170 -0
- data/spec/chewy/rspec/build_query_spec.rb +34 -0
- data/spec/chewy/rspec/helpers_spec.rb +61 -0
- data/spec/chewy/search/pagination/kaminari_examples.rb +1 -1
- data/spec/chewy/search/pagination/kaminari_spec.rb +1 -1
- data/spec/chewy/search/parameters/collapse_spec.rb +5 -0
- data/spec/chewy/search/parameters/ignore_unavailable_spec.rb +67 -0
- data/spec/chewy/search/parameters/knn_spec.rb +5 -0
- data/spec/chewy/search/parameters/order_spec.rb +18 -11
- data/spec/chewy/search/parameters/track_total_hits_spec.rb +5 -0
- data/spec/chewy/search/parameters_spec.rb +6 -1
- data/spec/chewy/search/request_spec.rb +58 -9
- data/spec/chewy/search_spec.rb +9 -0
- data/spec/chewy/strategy/active_job_spec.rb +8 -8
- data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
- data/spec/chewy/strategy/delayed_sidekiq_spec.rb +208 -0
- data/spec/chewy/strategy/lazy_sidekiq_spec.rb +214 -0
- data/spec/chewy/strategy/sidekiq_spec.rb +4 -4
- data/spec/chewy_spec.rb +10 -7
- data/spec/spec_helper.rb +1 -2
- data/spec/support/active_record.rb +8 -1
- metadata +45 -264
- data/lib/chewy/backports/deep_dup.rb +0 -46
- data/lib/chewy/backports/duplicable.rb +0 -91
- data/lib/chewy/index/import/thread_safe_progress_bar.rb +0 -40
data/lib/chewy/rake_helper.rb
CHANGED
@@ -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(
|
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(
|
184
|
-
|
185
|
-
|
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
|
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,
|
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 '
|
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
|
-
|
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 =
|
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
|
-
|
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.
|
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
|
232
|
+
end
|
226
233
|
end
|
227
234
|
|
228
235
|
def compare_attributes(expected, real)
|
data/lib/chewy/rspec.rb
CHANGED
@@ -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
|
-
|
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.
|
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
|
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(
|
53
|
-
res.
|
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? ?
|
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')).
|
2
|
-
Dir.glob(File.join(File.dirname(__FILE__), 'parameters', '*.rb')).
|
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
|
data/lib/chewy/search/request.rb
CHANGED
@@ -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
|
-
|
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).
|
780
|
-
# # => <PlacesIndex::Query {..., :body=>{:
|
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).
|
791
|
-
# # => <PlacesIndex::Query {..., :body=>{:
|
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]
|
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(
|
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)
|