chewy 6.0.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +16 -0
- data/.github/dependabot.yml +42 -0
- data/.github/workflows/ruby.yml +60 -0
- data/.rubocop.yml +16 -8
- data/.rubocop_todo.yml +110 -22
- data/CHANGELOG.md +396 -105
- data/CODE_OF_CONDUCT.md +14 -0
- data/CONTRIBUTING.md +63 -0
- data/Gemfile +4 -10
- data/Guardfile +3 -1
- data/README.md +497 -275
- data/chewy.gemspec +5 -20
- data/gemfiles/base.gemfile +12 -0
- data/gemfiles/rails.6.1.activerecord.gemfile +10 -15
- data/gemfiles/rails.7.0.activerecord.gemfile +14 -0
- data/gemfiles/rails.7.1.activerecord.gemfile +14 -0
- data/lib/chewy/config.rb +60 -52
- data/lib/chewy/elastic_client.rb +31 -0
- data/lib/chewy/errors.rb +7 -10
- data/lib/chewy/fields/base.rb +79 -13
- data/lib/chewy/fields/root.rb +4 -14
- data/lib/chewy/index/actions.rb +54 -37
- data/lib/chewy/{type → index}/adapter/active_record.rb +30 -6
- data/lib/chewy/{type → index}/adapter/base.rb +2 -3
- data/lib/chewy/{type → index}/adapter/object.rb +27 -31
- data/lib/chewy/{type → index}/adapter/orm.rb +17 -18
- data/lib/chewy/index/aliases.rb +14 -5
- data/lib/chewy/index/crutch.rb +40 -0
- data/lib/chewy/index/import/bulk_builder.rb +311 -0
- data/lib/chewy/{type → index}/import/bulk_request.rb +6 -7
- data/lib/chewy/{type → index}/import/journal_builder.rb +11 -12
- data/lib/chewy/{type → index}/import/routine.rb +18 -17
- data/lib/chewy/{type → index}/import.rb +76 -32
- data/lib/chewy/{type → index}/mapping.rb +29 -34
- 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 +17 -0
- data/lib/chewy/index/specification.rb +1 -0
- data/lib/chewy/{type → index}/syncer.rb +59 -59
- data/lib/chewy/{type → index}/witchcraft.rb +11 -7
- data/lib/chewy/{type → index}/wrapper.rb +2 -2
- data/lib/chewy/index.rb +67 -94
- data/lib/chewy/journal.rb +25 -14
- data/lib/chewy/log_subscriber.rb +5 -1
- data/lib/chewy/minitest/helpers.rb +86 -13
- data/lib/chewy/minitest/search_index_receiver.rb +24 -26
- data/lib/chewy/railtie.rb +6 -20
- data/lib/chewy/rake_helper.rb +169 -113
- data/lib/chewy/rspec/build_query.rb +12 -0
- data/lib/chewy/rspec/helpers.rb +55 -0
- data/lib/chewy/rspec/update_index.rb +55 -44
- 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/loader.rb +19 -41
- data/lib/chewy/search/parameters/collapse.rb +16 -0
- data/lib/chewy/search/parameters/concerns/query_storage.rb +2 -2
- data/lib/chewy/search/parameters/ignore_unavailable.rb +27 -0
- data/lib/chewy/search/parameters/indices.rb +13 -58
- data/lib/chewy/search/parameters/knn.rb +16 -0
- data/lib/chewy/search/parameters/order.rb +6 -19
- data/lib/chewy/search/parameters/source.rb +5 -1
- 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 +6 -4
- data/lib/chewy/search/query_proxy.rb +9 -2
- data/lib/chewy/search/request.rb +169 -134
- data/lib/chewy/search/response.rb +5 -5
- data/lib/chewy/search/scoping.rb +7 -8
- data/lib/chewy/search/scrolling.rb +13 -13
- data/lib/chewy/search.rb +9 -19
- data/lib/chewy/stash.rb +19 -30
- 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 +2 -1
- data/lib/chewy/strategy.rb +6 -19
- data/lib/chewy/version.rb +1 -1
- data/lib/chewy.rb +39 -86
- data/lib/generators/chewy/install_generator.rb +1 -1
- data/lib/tasks/chewy.rake +36 -32
- data/migration_guide.md +46 -8
- data/spec/chewy/config_spec.rb +16 -41
- data/spec/chewy/elastic_client_spec.rb +26 -0
- data/spec/chewy/fields/base_spec.rb +432 -147
- data/spec/chewy/fields/root_spec.rb +20 -28
- data/spec/chewy/fields/time_fields_spec.rb +5 -5
- data/spec/chewy/index/actions_spec.rb +368 -59
- data/spec/chewy/{type → index}/adapter/active_record_spec.rb +156 -40
- data/spec/chewy/{type → index}/adapter/object_spec.rb +21 -6
- data/spec/chewy/index/aliases_spec.rb +3 -3
- data/spec/chewy/index/import/bulk_builder_spec.rb +494 -0
- data/spec/chewy/{type → index}/import/bulk_request_spec.rb +5 -12
- data/spec/chewy/{type → index}/import/journal_builder_spec.rb +9 -19
- data/spec/chewy/{type → index}/import/routine_spec.rb +19 -19
- data/spec/chewy/{type → index}/import_spec.rb +164 -98
- data/spec/chewy/index/mapping_spec.rb +135 -0
- 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 +143 -0
- data/spec/chewy/index/settings_spec.rb +3 -1
- data/spec/chewy/index/specification_spec.rb +20 -30
- data/spec/chewy/{type → index}/syncer_spec.rb +14 -19
- data/spec/chewy/{type → index}/witchcraft_spec.rb +20 -22
- data/spec/chewy/index/wrapper_spec.rb +100 -0
- data/spec/chewy/index_spec.rb +60 -105
- data/spec/chewy/journal_spec.rb +25 -74
- data/spec/chewy/minitest/helpers_spec.rb +123 -15
- data/spec/chewy/minitest/search_index_receiver_spec.rb +28 -30
- data/spec/chewy/multi_search_spec.rb +4 -5
- data/spec/chewy/rake_helper_spec.rb +315 -55
- data/spec/chewy/rspec/build_query_spec.rb +34 -0
- data/spec/chewy/rspec/helpers_spec.rb +61 -0
- data/spec/chewy/rspec/update_index_spec.rb +74 -71
- data/spec/chewy/runtime_spec.rb +2 -2
- data/spec/chewy/search/loader_spec.rb +19 -53
- data/spec/chewy/search/pagination/kaminari_examples.rb +4 -6
- data/spec/chewy/search/pagination/kaminari_spec.rb +2 -2
- 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/indices_spec.rb +26 -117
- 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/query_storage_examples.rb +67 -21
- data/spec/chewy/search/parameters/search_after_spec.rb +4 -1
- data/spec/chewy/search/parameters/source_spec.rb +8 -2
- data/spec/chewy/search/parameters/track_total_hits_spec.rb +5 -0
- data/spec/chewy/search/parameters_spec.rb +18 -4
- data/spec/chewy/search/query_proxy_spec.rb +68 -17
- data/spec/chewy/search/request_spec.rb +292 -110
- data/spec/chewy/search/response_spec.rb +12 -12
- data/spec/chewy/search/scrolling_spec.rb +10 -17
- data/spec/chewy/search_spec.rb +40 -34
- data/spec/chewy/stash_spec.rb +9 -21
- data/spec/chewy/strategy/active_job_spec.rb +16 -16
- data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
- data/spec/chewy/strategy/atomic_spec.rb +9 -10
- 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 +12 -12
- data/spec/chewy/strategy_spec.rb +19 -15
- data/spec/chewy_spec.rb +24 -107
- data/spec/spec_helper.rb +3 -22
- data/spec/support/active_record.rb +25 -7
- metadata +78 -339
- data/.circleci/config.yml +0 -240
- data/Appraisals +0 -81
- data/gemfiles/rails.5.2.activerecord.gemfile +0 -17
- data/gemfiles/rails.5.2.mongoid.6.4.gemfile +0 -17
- data/gemfiles/rails.6.0.activerecord.gemfile +0 -17
- data/gemfiles/sequel.4.45.gemfile +0 -11
- data/lib/chewy/backports/deep_dup.rb +0 -46
- data/lib/chewy/backports/duplicable.rb +0 -91
- data/lib/chewy/search/pagination/will_paginate.rb +0 -43
- data/lib/chewy/search/parameters/types.rb +0 -20
- data/lib/chewy/strategy/resque.rb +0 -27
- data/lib/chewy/strategy/shoryuken.rb +0 -40
- data/lib/chewy/type/actions.rb +0 -43
- data/lib/chewy/type/adapter/mongoid.rb +0 -67
- data/lib/chewy/type/adapter/sequel.rb +0 -93
- data/lib/chewy/type/crutch.rb +0 -32
- data/lib/chewy/type/import/bulk_builder.rb +0 -122
- data/lib/chewy/type/observe.rb +0 -82
- data/lib/chewy/type.rb +0 -120
- data/lib/sequel/plugins/chewy_observe.rb +0 -63
- data/spec/chewy/search/pagination/will_paginate_examples.rb +0 -63
- data/spec/chewy/search/pagination/will_paginate_spec.rb +0 -23
- data/spec/chewy/search/parameters/types_spec.rb +0 -5
- data/spec/chewy/strategy/resque_spec.rb +0 -46
- data/spec/chewy/strategy/shoryuken_spec.rb +0 -70
- data/spec/chewy/type/actions_spec.rb +0 -50
- data/spec/chewy/type/adapter/mongoid_spec.rb +0 -372
- data/spec/chewy/type/adapter/sequel_spec.rb +0 -472
- data/spec/chewy/type/import/bulk_builder_spec.rb +0 -194
- data/spec/chewy/type/mapping_spec.rb +0 -175
- data/spec/chewy/type/observe_spec.rb +0 -137
- data/spec/chewy/type/wrapper_spec.rb +0 -100
- data/spec/chewy/type_spec.rb +0 -55
- data/spec/support/mongoid.rb +0 -93
- data/spec/support/sequel.rb +0 -80
@@ -1,5 +1,5 @@
|
|
1
1
|
module Chewy
|
2
|
-
class
|
2
|
+
class Index
|
3
3
|
# This class is able to find missing and outdated documents in the ES
|
4
4
|
# comparing ids from the data source and the ES index. Also, if `outdated_sync_field`
|
5
5
|
# exists in the index definition, it performs comparison of this field
|
@@ -9,10 +9,10 @@ module Chewy
|
|
9
9
|
# should be reindexed.
|
10
10
|
#
|
11
11
|
# To fetch necessary data from the source it uses adapter method
|
12
|
-
# {Chewy::
|
12
|
+
# {Chewy::Index::Adapter::Base#import_fields}, in case when the Object
|
13
13
|
# adapter is used it makes sense to read corresponding documentation.
|
14
14
|
#
|
15
|
-
# If `parallel` option is passed to the initializer - it will fetch
|
15
|
+
# If `parallel` option is passed to the initializer - it will fetch source and
|
16
16
|
# index data in parallel and then perform outdated objects calculation in
|
17
17
|
# parallel processes. Also, further import (if required) will be performed
|
18
18
|
# in parallel as well.
|
@@ -24,17 +24,17 @@ module Chewy
|
|
24
24
|
# ATTENTION: synchronization may be slow in case when synchronized tables
|
25
25
|
# are missing compound index on primary key and `outdated_sync_field`.
|
26
26
|
#
|
27
|
-
# @see Chewy::
|
27
|
+
# @see Chewy::Index::Actions::ClassMethods#sync
|
28
28
|
class Syncer
|
29
29
|
DEFAULT_SYNC_BATCH_SIZE = 20_000
|
30
30
|
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
31
|
-
OUTDATED_IDS_WORKER = lambda do |outdated_sync_field_type, source_data_hash,
|
32
|
-
::Process.setproctitle("chewy [#{
|
31
|
+
OUTDATED_IDS_WORKER = lambda do |outdated_sync_field_type, source_data_hash, index, total, index_data|
|
32
|
+
::Process.setproctitle("chewy [#{index}]: sync outdated calculation (#{::Parallel.worker_number + 1}/#{total})") if index
|
33
33
|
index_data.each_with_object([]) do |(id, index_sync_value), result|
|
34
34
|
next unless source_data_hash[id]
|
35
35
|
|
36
36
|
outdated = if outdated_sync_field_type == 'date'
|
37
|
-
!Chewy::
|
37
|
+
!Chewy::Index::Syncer.dates_equal(typecast_date(source_data_hash[id]), Time.iso8601(index_sync_value))
|
38
38
|
else
|
39
39
|
source_data_hash[id] != index_sync_value
|
40
40
|
end
|
@@ -42,8 +42,8 @@ module Chewy
|
|
42
42
|
result.push(id) if outdated
|
43
43
|
end
|
44
44
|
end
|
45
|
-
SOURCE_OR_INDEX_DATA_WORKER = lambda do |syncer,
|
46
|
-
::Process.setproctitle("chewy [#{
|
45
|
+
SOURCE_OR_INDEX_DATA_WORKER = lambda do |syncer, index, kind|
|
46
|
+
::Process.setproctitle("chewy [#{index}]: sync fetching data (#{kind})")
|
47
47
|
result = case kind
|
48
48
|
when :source
|
49
49
|
syncer.send(:fetch_source_data)
|
@@ -56,7 +56,10 @@ module Chewy
|
|
56
56
|
def self.typecast_date(string)
|
57
57
|
if string.is_a?(String) && (match = ISO_DATETIME.match(string))
|
58
58
|
microsec = (match[7].to_r * 1_000_000).to_i
|
59
|
-
|
59
|
+
day = "#{match[1]}-#{match[2]}-#{match[3]}"
|
60
|
+
time_with_seconds = "#{match[4]}:#{match[5]}:#{match[6]}"
|
61
|
+
microseconds = format('%06d', microsec)
|
62
|
+
date = "#{day}T#{time_with_seconds}.#{microseconds}+00:00"
|
60
63
|
Time.iso8601(date)
|
61
64
|
else
|
62
65
|
string
|
@@ -68,18 +71,10 @@ module Chewy
|
|
68
71
|
[one.to_i, one.usec / 1000] == [two.to_i, two.usec / 1000]
|
69
72
|
end
|
70
73
|
|
71
|
-
#
|
72
|
-
# milliseconds, so ES stored time with the seconds precision.
|
73
|
-
if ActiveSupport::VERSION::STRING < '4.1.0'
|
74
|
-
def self.dates_equal(one, two)
|
75
|
-
one.to_i == two.to_i
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
# @param type [Chewy::Type] chewy type
|
74
|
+
# @param index [Chewy::Index] chewy index
|
80
75
|
# @param parallel [true, Integer, Hash] options for parallel execution or the number of processes
|
81
|
-
def initialize(
|
82
|
-
@
|
76
|
+
def initialize(index, parallel: nil)
|
77
|
+
@index = index
|
83
78
|
@parallel = if !parallel || parallel.is_a?(Hash)
|
84
79
|
parallel
|
85
80
|
elsif parallel.is_a?(Integer)
|
@@ -95,7 +90,8 @@ module Chewy
|
|
95
90
|
def perform
|
96
91
|
ids = missing_ids | outdated_ids
|
97
92
|
return 0 if ids.blank?
|
98
|
-
|
93
|
+
|
94
|
+
@index.import(ids, parallel: @parallel) && ids.count
|
99
95
|
end
|
100
96
|
|
101
97
|
# Finds ids of all the objects that are not indexed yet or deleted
|
@@ -113,21 +109,19 @@ module Chewy
|
|
113
109
|
end
|
114
110
|
end
|
115
111
|
|
116
|
-
# If
|
117
|
-
#
|
118
|
-
# and index and returns the ids of entities which
|
119
|
-
# different values there.
|
112
|
+
# If index supports outdated sync, it compares the values of the
|
113
|
+
# `outdated_sync_field` for each object and document in the source
|
114
|
+
# and index and returns the ids of entities which differ.
|
120
115
|
#
|
121
|
-
# @see Chewy::
|
116
|
+
# @see Chewy::Index::Mapping::ClassMethods#supports_outdated_sync?
|
122
117
|
# @return [Array<String>] an array of outdated ids
|
123
118
|
def outdated_ids
|
124
|
-
return [] if source_data.blank? || index_data.blank? || !@
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
end
|
119
|
+
return [] if source_data.blank? || index_data.blank? || !@index.supports_outdated_sync?
|
120
|
+
|
121
|
+
@outdated_ids ||= if @parallel
|
122
|
+
parallel_outdated_ids
|
123
|
+
else
|
124
|
+
linear_outdated_ids
|
131
125
|
end
|
132
126
|
end
|
133
127
|
|
@@ -142,44 +136,48 @@ module Chewy
|
|
142
136
|
end
|
143
137
|
|
144
138
|
def source_and_index_data
|
145
|
-
@source_and_index_data ||=
|
146
|
-
if
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
[result.first.values.first, result.second.values.first]
|
152
|
-
else
|
153
|
-
[result.second.values.first, result.first.values.first]
|
154
|
-
end
|
139
|
+
@source_and_index_data ||= if @parallel
|
140
|
+
::ActiveRecord::Base.connection.close if defined?(::ActiveRecord::Base)
|
141
|
+
result = ::Parallel.map(%i[source index], @parallel, &SOURCE_OR_INDEX_DATA_WORKER.curry[self, @index])
|
142
|
+
::ActiveRecord::Base.connection.reconnect! if defined?(::ActiveRecord::Base)
|
143
|
+
if result.first.keys.first == :source
|
144
|
+
[result.first.values.first, result.second.values.first]
|
155
145
|
else
|
156
|
-
[
|
146
|
+
[result.second.values.first, result.first.values.first]
|
157
147
|
end
|
148
|
+
else
|
149
|
+
[fetch_source_data, fetch_index_data]
|
158
150
|
end
|
159
151
|
end
|
160
152
|
|
161
153
|
def fetch_source_data
|
162
|
-
if @
|
163
|
-
|
154
|
+
if @index.supports_outdated_sync?
|
155
|
+
import_fields_args = {
|
156
|
+
fields: [@index.outdated_sync_field],
|
157
|
+
batch_size: DEFAULT_SYNC_BATCH_SIZE,
|
158
|
+
typecast: false
|
159
|
+
}
|
160
|
+
@index.adapter.import_fields(import_fields_args).to_a.flatten(1).each do |data|
|
164
161
|
data[0] = data[0].to_s
|
165
162
|
end
|
166
163
|
else
|
167
|
-
@
|
164
|
+
@index.adapter.import_fields(batch_size: DEFAULT_SYNC_BATCH_SIZE, typecast: false).to_a.flatten(1).map(&:to_s)
|
168
165
|
end
|
169
166
|
end
|
170
167
|
|
171
168
|
def fetch_index_data
|
172
|
-
if @
|
173
|
-
@
|
169
|
+
if @index.supports_outdated_sync?
|
170
|
+
@index.pluck(:_id, @index.outdated_sync_field).each do |data|
|
174
171
|
data[0] = data[0].to_s
|
175
172
|
end
|
176
173
|
else
|
177
|
-
@
|
174
|
+
@index.pluck(:_id).map(&:to_s)
|
178
175
|
end
|
179
176
|
end
|
180
177
|
|
181
178
|
def data_ids(data)
|
182
|
-
return data unless @
|
179
|
+
return data unless @index.supports_outdated_sync?
|
180
|
+
|
183
181
|
data.map(&:first)
|
184
182
|
end
|
185
183
|
|
@@ -192,7 +190,12 @@ module Chewy
|
|
192
190
|
batches = index_data.each_slice(size)
|
193
191
|
|
194
192
|
::ActiveRecord::Base.connection.close if defined?(::ActiveRecord::Base)
|
195
|
-
|
193
|
+
curried_outdated_ids_worker = OUTDATED_IDS_WORKER.curry[outdated_sync_field_type, source_data.to_h, @index, batches.size]
|
194
|
+
result = ::Parallel.map(
|
195
|
+
batches,
|
196
|
+
@parallel,
|
197
|
+
&curried_outdated_ids_worker
|
198
|
+
).flatten(1)
|
196
199
|
::ActiveRecord::Base.connection.reconnect! if defined?(::ActiveRecord::Base)
|
197
200
|
result
|
198
201
|
end
|
@@ -203,16 +206,13 @@ module Chewy
|
|
203
206
|
|
204
207
|
def outdated_sync_field_type
|
205
208
|
return @outdated_sync_field_type if instance_variable_defined?(:@outdated_sync_field_type)
|
206
|
-
return unless @
|
209
|
+
return unless @index.outdated_sync_field
|
207
210
|
|
208
|
-
|
209
|
-
args[:include_type_name] = true if Runtime.version >= '6.7.0'
|
210
|
-
mappings = @type.client.indices.get_mapping(**args).values.first.fetch('mappings', {})
|
211
|
+
mappings = @index.client.indices.get_mapping(index: @index.index_name).values.first.fetch('mappings', {})
|
211
212
|
|
212
213
|
@outdated_sync_field_type = mappings
|
213
|
-
.fetch(@type.type_name, {})
|
214
214
|
.fetch('properties', {})
|
215
|
-
.fetch(@
|
215
|
+
.fetch(@index.outdated_sync_field.to_s, {})['type']
|
216
216
|
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
217
217
|
nil
|
218
218
|
end
|
@@ -7,7 +7,7 @@ rescue LoadError
|
|
7
7
|
end
|
8
8
|
|
9
9
|
module Chewy
|
10
|
-
class
|
10
|
+
class Index
|
11
11
|
module Witchcraft
|
12
12
|
extend ActiveSupport::Concern
|
13
13
|
|
@@ -43,10 +43,10 @@ module Chewy
|
|
43
43
|
class Cauldron
|
44
44
|
attr_reader :locals
|
45
45
|
|
46
|
-
# @param
|
46
|
+
# @param index [Chewy::Index] index for composition
|
47
47
|
# @param fields [Array<Symbol>] restricts the fields for composition
|
48
|
-
def initialize(
|
49
|
-
@
|
48
|
+
def initialize(index, fields: [])
|
49
|
+
@index = index
|
50
50
|
@locals = []
|
51
51
|
@fields = fields
|
52
52
|
end
|
@@ -60,7 +60,7 @@ module Chewy
|
|
60
60
|
def alicorn
|
61
61
|
@alicorn ||= singleton_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
62
62
|
-> (locals, object0, crutches) do
|
63
|
-
#{composed_values(@
|
63
|
+
#{composed_values(@index.root, 0)}
|
64
64
|
end
|
65
65
|
RUBY
|
66
66
|
end
|
@@ -141,6 +141,7 @@ module Chewy
|
|
141
141
|
|
142
142
|
def non_proc_fields_for(parent, nesting)
|
143
143
|
return [] unless parent
|
144
|
+
|
144
145
|
fields = (parent.children || []).reject { |field| field.value.is_a?(Proc) }
|
145
146
|
|
146
147
|
if nesting.zero? && @fields.present?
|
@@ -152,6 +153,7 @@ module Chewy
|
|
152
153
|
|
153
154
|
def proc_fields_for(parent, nesting)
|
154
155
|
return [] unless parent
|
156
|
+
|
155
157
|
fields = (parent.children || []).select { |field| field.value.is_a?(Proc) }
|
156
158
|
|
157
159
|
if nesting.zero? && @fields.present?
|
@@ -173,7 +175,7 @@ module Chewy
|
|
173
175
|
if proc.arity.zero?
|
174
176
|
source = replace_self(source, :"object#{nesting}")
|
175
177
|
source = replace_send(source, :"object#{nesting}")
|
176
|
-
elsif proc.arity
|
178
|
+
elsif proc.arity.negative?
|
177
179
|
raise "Splat arguments are unsupported by witchcraft:\n`#{proc.source}"
|
178
180
|
else
|
179
181
|
(nesting + 1).times do |n|
|
@@ -192,6 +194,7 @@ module Chewy
|
|
192
194
|
|
193
195
|
def exctract_lambdas(node)
|
194
196
|
return unless node.is_a?(Parser::AST::Node)
|
197
|
+
|
195
198
|
if node.type == :block && node.children[0].type == :send && node.children[0].to_a == [nil, :lambda]
|
196
199
|
[node.children[2]]
|
197
200
|
else
|
@@ -214,7 +217,7 @@ module Chewy
|
|
214
217
|
def replace_send(node, variable)
|
215
218
|
if node.is_a?(Parser::AST::Node)
|
216
219
|
if node.type == :send && node.children[0].nil?
|
217
|
-
node.updated(nil, [Parser::AST::Node.new(:lvar, [variable]), *node.children[1
|
220
|
+
node.updated(nil, [Parser::AST::Node.new(:lvar, [variable]), *node.children[1..]])
|
218
221
|
else
|
219
222
|
node.updated(nil, node.children.map { |child| replace_send(child, variable) })
|
220
223
|
end
|
@@ -253,6 +256,7 @@ module Chewy
|
|
253
256
|
|
254
257
|
def binding_variable_list(node)
|
255
258
|
return unless node.is_a?(Parser::AST::Node)
|
259
|
+
|
256
260
|
if node.type == :send && node.children[0].nil?
|
257
261
|
node.children[1]
|
258
262
|
else
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Chewy
|
2
|
-
class
|
2
|
+
class Index
|
3
3
|
module Wrapper
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
@@ -28,7 +28,7 @@ module Chewy
|
|
28
28
|
def ==(other)
|
29
29
|
return true if super
|
30
30
|
|
31
|
-
if other.is_a?(Chewy::
|
31
|
+
if other.is_a?(Chewy::Index)
|
32
32
|
self.class == other.class && (respond_to?(:id) ? id == other.id : attributes == other.attributes)
|
33
33
|
elsif other.respond_to?(:id)
|
34
34
|
self.class.adapter.target.is_a?(Class) &&
|
data/lib/chewy/index.rb
CHANGED
@@ -1,23 +1,52 @@
|
|
1
1
|
require 'chewy/search'
|
2
2
|
require 'chewy/index/actions'
|
3
|
+
require 'chewy/index/adapter/active_record'
|
4
|
+
require 'chewy/index/adapter/object'
|
3
5
|
require 'chewy/index/aliases'
|
6
|
+
require 'chewy/index/crutch'
|
7
|
+
require 'chewy/index/import'
|
8
|
+
require 'chewy/index/mapping'
|
9
|
+
require 'chewy/index/observe'
|
4
10
|
require 'chewy/index/settings'
|
5
11
|
require 'chewy/index/specification'
|
12
|
+
require 'chewy/index/syncer'
|
13
|
+
require 'chewy/index/witchcraft'
|
14
|
+
require 'chewy/index/wrapper'
|
6
15
|
|
7
16
|
module Chewy
|
8
17
|
class Index
|
18
|
+
IMPORT_OPTIONS_KEYS = %i[
|
19
|
+
batch_size bulk_size consistency direct_import journal
|
20
|
+
pipeline raw_import refresh replication
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
STRATEGY_OPTIONS = {
|
24
|
+
delayed_sidekiq: %i[latency margin ttl reindex_wrapper]
|
25
|
+
}.freeze
|
26
|
+
|
9
27
|
include Search
|
10
28
|
include Actions
|
11
29
|
include Aliases
|
30
|
+
include Import
|
31
|
+
include Mapping
|
32
|
+
include Observe
|
33
|
+
include Crutch
|
34
|
+
include Witchcraft
|
35
|
+
include Wrapper
|
12
36
|
|
13
37
|
singleton_class.delegate :client, to: 'Chewy'
|
14
38
|
|
15
|
-
class_attribute :
|
16
|
-
self.
|
39
|
+
class_attribute :adapter
|
40
|
+
self.adapter = Chewy::Index::Adapter::Object.new(:default)
|
41
|
+
|
42
|
+
class_attribute :index_scope_defined
|
17
43
|
|
18
44
|
class_attribute :_settings
|
19
45
|
self._settings = Chewy::Index::Settings.new
|
20
46
|
|
47
|
+
class_attribute :_default_import_options
|
48
|
+
self._default_import_options = {}
|
49
|
+
|
21
50
|
class << self
|
22
51
|
# @overload index_name(suggest)
|
23
52
|
# If suggested name is passed, it is set up as the new base name for
|
@@ -47,7 +76,8 @@ module Chewy
|
|
47
76
|
# UsersIndex.index_name(prefix: '', suffix: '2017') # => 'users_2017'
|
48
77
|
#
|
49
78
|
# @param prefix [String] index name prefix, uses {.prefix} method by default
|
50
|
-
# @param suffix [String] index name suffix, used for creating several indexes for the same
|
79
|
+
# @param suffix [String] index name suffix, used for creating several indexes for the same
|
80
|
+
# alias during the zero-downtime reset
|
51
81
|
# @raise [UndefinedIndex] if the base name is blank
|
52
82
|
# @return [String] result index name
|
53
83
|
def index_name(suggest = nil, prefix: nil, suffix: nil)
|
@@ -55,7 +85,7 @@ module Chewy
|
|
55
85
|
@base_name = suggest.to_s.presence
|
56
86
|
else
|
57
87
|
[
|
58
|
-
prefix ||
|
88
|
+
prefix || self.prefix,
|
59
89
|
base_name,
|
60
90
|
suffix
|
61
91
|
].reject(&:blank?).join('_')
|
@@ -77,6 +107,7 @@ module Chewy
|
|
77
107
|
def base_name
|
78
108
|
@base_name ||= name.sub(/Index\z/, '').demodulize.underscore if name
|
79
109
|
raise UndefinedIndex if @base_name.blank?
|
110
|
+
|
80
111
|
@base_name
|
81
112
|
end
|
82
113
|
|
@@ -111,80 +142,30 @@ module Chewy
|
|
111
142
|
Chewy.configuration[:prefix]
|
112
143
|
end
|
113
144
|
|
114
|
-
# Defines
|
145
|
+
# Defines scope and options for the index. Arguments depends on adapter used. For
|
115
146
|
# ActiveRecord you can pass model or scope and options
|
116
147
|
#
|
117
148
|
# class CarsIndex < Chewy::Index
|
118
|
-
#
|
119
|
-
#
|
120
|
-
# end # defines VehiclesIndex::Car type
|
121
|
-
# end
|
122
|
-
#
|
123
|
-
# Type name might be passed in complicated cases:
|
124
|
-
#
|
125
|
-
# class VehiclesIndex < Chewy::Index
|
126
|
-
# define_type Vehicle.cars.includes(:manufacturer), name: 'cars' do
|
127
|
-
# ...
|
128
|
-
# end # defines VehiclesIndex::Cars type
|
129
|
-
#
|
130
|
-
# define_type Vehicle.motocycles.includes(:manufacturer), name: 'motocycles' do
|
131
|
-
# ...
|
132
|
-
# end # defines VehiclesIndex::Motocycles type
|
149
|
+
# index_scope Car
|
150
|
+
# ...
|
133
151
|
# end
|
134
152
|
#
|
135
|
-
# For plain objects:
|
153
|
+
# For plain objects you can completely omit this directive, unless you need to specify some options:
|
136
154
|
#
|
137
155
|
# class PlanesIndex < Chewy::Index
|
138
|
-
#
|
139
|
-
# ...
|
140
|
-
# end # defines PlanesIndex::Plane type
|
156
|
+
# ...
|
141
157
|
# end
|
142
158
|
#
|
143
159
|
# The main difference between using plain objects or ActiveRecord models for indexing
|
144
|
-
# is import. If you will call `CarsIndex
|
145
|
-
# automatically, while `PlanesIndex
|
160
|
+
# is import. If you will call `CarsIndex.import` - it will import all the cars
|
161
|
+
# automatically, while `PlanesIndex.import(my_planes)` requires import data to be
|
146
162
|
# passed.
|
147
163
|
#
|
148
|
-
def
|
149
|
-
raise '
|
150
|
-
|
151
|
-
type_class = Chewy.create_type(self, target, options, &block)
|
152
|
-
self.type_hash = type_hash.merge(type_class.type_name => type_class)
|
153
|
-
end
|
154
|
-
|
155
|
-
# Types method has double usage.
|
156
|
-
# If no arguments are passed - it returns array of defined types:
|
157
|
-
#
|
158
|
-
# UsersIndex.types # => [UsersIndex::Admin, UsersIndex::Manager, UsersIndex::User]
|
159
|
-
#
|
160
|
-
# If arguments are passed it treats like a part of chainable query DSL and
|
161
|
-
# adds types array for index to select.
|
162
|
-
#
|
163
|
-
# UsersIndex.filters { name =~ 'ro' }.types(:admin, :manager)
|
164
|
-
# UsersIndex.types(:admin, :manager).filters { name =~ 'ro' } # the same as the first example
|
165
|
-
#
|
166
|
-
def types(*args)
|
167
|
-
if args.present?
|
168
|
-
all.types(*args)
|
169
|
-
else
|
170
|
-
type_hash.values
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
# Returns defined types names:
|
175
|
-
#
|
176
|
-
# UsersIndex.type_names # => ['admin', 'manager', 'user']
|
177
|
-
#
|
178
|
-
def type_names
|
179
|
-
type_hash.keys
|
180
|
-
end
|
164
|
+
def index_scope(target, options = {})
|
165
|
+
raise 'Index scope is already defined' if index_scope_defined?
|
181
166
|
|
182
|
-
|
183
|
-
|
184
|
-
# UserIndex.type('admin') # => UsersIndex::Admin
|
185
|
-
#
|
186
|
-
def type(type_name)
|
187
|
-
type_hash.fetch(type_name) { raise UndefinedType, "Unknown type in #{name}: #{type_name}" }
|
167
|
+
self.adapter = Chewy.adapters.find { |klass| klass.accepts?(target) }.new(target, **options)
|
168
|
+
self.index_scope_defined = true
|
188
169
|
end
|
189
170
|
|
190
171
|
# Used as a part of index definition DSL. Defines settings:
|
@@ -221,7 +202,7 @@ module Chewy
|
|
221
202
|
end
|
222
203
|
|
223
204
|
def mappings_hash
|
224
|
-
mappings =
|
205
|
+
mappings = root.mappings_hash
|
225
206
|
mappings.present? ? {mappings: mappings} : {}
|
226
207
|
end
|
227
208
|
|
@@ -234,44 +215,36 @@ module Chewy
|
|
234
215
|
[settings_hash, mappings_hash].inject(:merge)
|
235
216
|
end
|
236
217
|
|
237
|
-
def index_params
|
238
|
-
ActiveSupport::Deprecation.warn '`Chewy::Index.index_params` is deprecated and will be removed soon, use `Chewy::Index.specification_hash`'
|
239
|
-
specification_hash
|
240
|
-
end
|
241
|
-
|
242
218
|
# @see Chewy::Index::Specification
|
243
219
|
# @return [Chewy::Index::Specification] a specification object instance for this particular index
|
244
220
|
def specification
|
245
221
|
@specification ||= Specification.new(self)
|
246
222
|
end
|
247
223
|
|
248
|
-
def
|
249
|
-
|
250
|
-
|
224
|
+
def default_import_options(params)
|
225
|
+
params.assert_valid_keys(IMPORT_OPTIONS_KEYS)
|
226
|
+
self._default_import_options = _default_import_options.merge(params)
|
251
227
|
end
|
252
228
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
ActiveSupport::Deprecation.warn '`Chewy::Index.default_prefix` is deprecated and will be removed soon, use `Chewy::Index.prefix` instead'
|
257
|
-
prefix
|
258
|
-
else
|
259
|
-
super
|
260
|
-
end
|
261
|
-
end
|
229
|
+
def strategy_config(params = {})
|
230
|
+
@strategy_config ||= begin
|
231
|
+
config_struct = Struct.new(*STRATEGY_OPTIONS.keys).new
|
262
232
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
end
|
233
|
+
STRATEGY_OPTIONS.each_with_object(config_struct) do |(strategy, options), res|
|
234
|
+
res[strategy] = case strategy
|
235
|
+
when :delayed_sidekiq
|
236
|
+
Struct.new(*STRATEGY_OPTIONS[strategy]).new.tap do |config|
|
237
|
+
options.each do |option|
|
238
|
+
config[option] = params.dig(strategy, option) || Chewy.configuration.dig(:strategy_config, strategy, option)
|
239
|
+
end
|
271
240
|
|
272
|
-
|
273
|
-
|
274
|
-
|
241
|
+
config[:reindex_wrapper] ||= ->(&reindex) { reindex.call } # default wrapper
|
242
|
+
end
|
243
|
+
else
|
244
|
+
raise NotImplementedError, "Unsupported strategy: '#{strategy}'"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
275
248
|
end
|
276
249
|
end
|
277
250
|
end
|
data/lib/chewy/journal.rb
CHANGED
@@ -2,33 +2,36 @@ module Chewy
|
|
2
2
|
# A class to perform journal-related actions for the specified indexes/types.
|
3
3
|
#
|
4
4
|
# @example
|
5
|
-
# journal = Chewy::Journal.new('places
|
5
|
+
# journal = Chewy::Journal.new('places', UsersIndex)
|
6
6
|
# journal.apply(20.minutes.ago)
|
7
7
|
# journal.clean
|
8
8
|
#
|
9
9
|
class Journal
|
10
|
-
# @param only [Array<String, Chewy::Index
|
10
|
+
# @param only [Array<String, Chewy::Index>] indexes or string references to perform actions on
|
11
11
|
def initialize(*only)
|
12
12
|
@only = only
|
13
13
|
end
|
14
14
|
|
15
15
|
# Applies all changes that were done since the specified time to the
|
16
|
-
# specified indexes
|
16
|
+
# specified indexes.
|
17
17
|
#
|
18
18
|
# @param since_time [Time, DateTime] timestamp from which changes will be applied
|
19
|
-
# @param
|
19
|
+
# @param fetch_limit [Int] amount of entries to be fetched on each cycle
|
20
20
|
# @return [Integer] the amount of journal entries found
|
21
|
-
def apply(since_time,
|
21
|
+
def apply(since_time, fetch_limit: 10, **import_options)
|
22
22
|
stage = 1
|
23
23
|
since_time -= 1
|
24
24
|
count = 0
|
25
|
-
|
26
|
-
|
25
|
+
|
26
|
+
total_count = entries(since_time, fetch_limit).total_count
|
27
|
+
|
28
|
+
while count < total_count
|
29
|
+
entries = entries(since_time, fetch_limit).to_a.presence or break
|
27
30
|
count += entries.size
|
28
31
|
groups = reference_groups(entries)
|
29
32
|
ActiveSupport::Notifications.instrument 'apply_journal.chewy', stage: stage, groups: groups
|
30
|
-
groups.each do |
|
31
|
-
|
33
|
+
groups.each do |index, references|
|
34
|
+
index.import(references, import_options.merge(journal: false))
|
32
35
|
end
|
33
36
|
stage += 1
|
34
37
|
since_time = entries.map(&:created_at).max
|
@@ -40,16 +43,24 @@ module Chewy
|
|
40
43
|
#
|
41
44
|
# @param until_time [Time, DateTime] time to clean up until it
|
42
45
|
# @return [Hash] delete_by_query ES API call result
|
43
|
-
def clean(until_time = nil)
|
44
|
-
Chewy::Stash::Journal.clean(
|
46
|
+
def clean(until_time = nil, delete_by_query_options: {})
|
47
|
+
Chewy::Stash::Journal.clean(
|
48
|
+
until_time,
|
49
|
+
only: @only,
|
50
|
+
delete_by_query_options: delete_by_query_options.merge(refresh: false)
|
51
|
+
)
|
45
52
|
end
|
46
53
|
|
47
54
|
private
|
48
55
|
|
56
|
+
def entries(since_time, fetch_limit)
|
57
|
+
Chewy::Stash::Journal.entries(since_time, only: @only).order(:created_at).limit(fetch_limit)
|
58
|
+
end
|
59
|
+
|
49
60
|
def reference_groups(entries)
|
50
|
-
entries.group_by(&:
|
51
|
-
|
52
|
-
|
61
|
+
entries.group_by(&:index_name)
|
62
|
+
.transform_keys { |index_name| Chewy.derive_name(index_name) }
|
63
|
+
.transform_values { |grouped_entries| grouped_entries.map(&:references).inject(:|) }
|
53
64
|
end
|
54
65
|
end
|
55
66
|
end
|
data/lib/chewy/log_subscriber.rb
CHANGED
@@ -24,7 +24,11 @@ module Chewy
|
|
24
24
|
|
25
25
|
subject = payload[:type].presence || payload[:index]
|
26
26
|
action = "#{subject} #{action} (#{event.duration.round(1)}ms)"
|
27
|
-
action =
|
27
|
+
action = if ActiveSupport.version >= Gem::Version.new('7.1')
|
28
|
+
color(action, GREEN, bold: true)
|
29
|
+
else
|
30
|
+
color(action, GREEN, true)
|
31
|
+
end
|
28
32
|
|
29
33
|
debug(" #{action} #{description}")
|
30
34
|
end
|