tiny-quick-gem 0.0.1
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 +7 -0
- data/searchkick-6.1.2/CHANGELOG.md +919 -0
- data/searchkick-6.1.2/LICENSE.txt +22 -0
- data/searchkick-6.1.2/README.md +2348 -0
- data/searchkick-6.1.2/lib/searchkick/bulk_reindex_job.rb +19 -0
- data/searchkick-6.1.2/lib/searchkick/controller_runtime.rb +40 -0
- data/searchkick-6.1.2/lib/searchkick/hash_wrapper.rb +41 -0
- data/searchkick-6.1.2/lib/searchkick/index.rb +480 -0
- data/searchkick-6.1.2/lib/searchkick/index_cache.rb +30 -0
- data/searchkick-6.1.2/lib/searchkick/index_options.rb +652 -0
- data/searchkick-6.1.2/lib/searchkick/indexer.rb +43 -0
- data/searchkick-6.1.2/lib/searchkick/log_subscriber.rb +57 -0
- data/searchkick-6.1.2/lib/searchkick/middleware.rb +19 -0
- data/searchkick-6.1.2/lib/searchkick/model.rb +120 -0
- data/searchkick-6.1.2/lib/searchkick/multi_search.rb +46 -0
- data/searchkick-6.1.2/lib/searchkick/process_batch_job.rb +20 -0
- data/searchkick-6.1.2/lib/searchkick/process_queue_job.rb +33 -0
- data/searchkick-6.1.2/lib/searchkick/query.rb +1372 -0
- data/searchkick-6.1.2/lib/searchkick/railtie.rb +7 -0
- data/searchkick-6.1.2/lib/searchkick/record_data.rb +147 -0
- data/searchkick-6.1.2/lib/searchkick/record_indexer.rb +174 -0
- data/searchkick-6.1.2/lib/searchkick/reindex_queue.rb +57 -0
- data/searchkick-6.1.2/lib/searchkick/reindex_v2_job.rb +17 -0
- data/searchkick-6.1.2/lib/searchkick/relation.rb +728 -0
- data/searchkick-6.1.2/lib/searchkick/relation_indexer.rb +184 -0
- data/searchkick-6.1.2/lib/searchkick/reranking.rb +28 -0
- data/searchkick-6.1.2/lib/searchkick/results.rb +359 -0
- data/searchkick-6.1.2/lib/searchkick/script.rb +11 -0
- data/searchkick-6.1.2/lib/searchkick/version.rb +3 -0
- data/searchkick-6.1.2/lib/searchkick/where.rb +11 -0
- data/searchkick-6.1.2/lib/searchkick.rb +388 -0
- data/searchkick-6.1.2/lib/tasks/searchkick.rake +37 -0
- data/tiny-quick-gem.gemspec +12 -0
- metadata +73 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Searchkick
|
|
2
|
+
class BulkReindexJob < Searchkick.parent_job.constantize
|
|
3
|
+
queue_as { Searchkick.queue_name }
|
|
4
|
+
|
|
5
|
+
def perform(class_name:, record_ids: nil, index_name: nil, method_name: nil, batch_id: nil, min_id: nil, max_id: nil, ignore_missing: nil)
|
|
6
|
+
model = Searchkick.load_model(class_name)
|
|
7
|
+
index = model.searchkick_index(name: index_name)
|
|
8
|
+
|
|
9
|
+
record_ids ||= min_id..max_id
|
|
10
|
+
|
|
11
|
+
relation = Searchkick.scope(model)
|
|
12
|
+
relation = Searchkick.load_records(relation, record_ids)
|
|
13
|
+
relation = relation.search_import if relation.respond_to?(:search_import)
|
|
14
|
+
|
|
15
|
+
RecordIndexer.new(index).reindex(relation, mode: :inline, method_name: method_name, ignore_missing: ignore_missing, full: false)
|
|
16
|
+
RelationIndexer.new(index).batch_completed(batch_id) if batch_id
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# based on https://gist.github.com/mnutt/566725
|
|
2
|
+
module Searchkick
|
|
3
|
+
module ControllerRuntime
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
attr_internal :searchkick_runtime
|
|
9
|
+
|
|
10
|
+
def process_action(action, *args)
|
|
11
|
+
# We also need to reset the runtime before each action
|
|
12
|
+
# because of queries in middleware or in cases we are streaming
|
|
13
|
+
# and it won't be cleaned up by the method below.
|
|
14
|
+
Searchkick::LogSubscriber.reset_runtime
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def cleanup_view_runtime
|
|
19
|
+
searchkick_rt_before_render = Searchkick::LogSubscriber.reset_runtime
|
|
20
|
+
runtime = super
|
|
21
|
+
searchkick_rt_after_render = Searchkick::LogSubscriber.reset_runtime
|
|
22
|
+
self.searchkick_runtime = searchkick_rt_before_render + searchkick_rt_after_render
|
|
23
|
+
runtime - searchkick_rt_after_render
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def append_info_to_payload(payload)
|
|
27
|
+
super
|
|
28
|
+
payload[:searchkick_runtime] = (searchkick_runtime || 0) + Searchkick::LogSubscriber.reset_runtime
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module ClassMethods
|
|
32
|
+
def log_process_action(payload)
|
|
33
|
+
messages = super
|
|
34
|
+
runtime = payload[:searchkick_runtime]
|
|
35
|
+
messages << ("Searchkick: %.1fms" % runtime.to_f) if runtime.to_f > 0
|
|
36
|
+
messages
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Searchkick
|
|
2
|
+
class HashWrapper
|
|
3
|
+
def initialize(attributes)
|
|
4
|
+
@attributes = attributes
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def [](name)
|
|
8
|
+
@attributes[name.to_s]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_h
|
|
12
|
+
@attributes
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def as_json(...)
|
|
16
|
+
@attributes.as_json(...)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_json(...)
|
|
20
|
+
@attributes.to_json(...)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def method_missing(name, ...)
|
|
24
|
+
if @attributes.key?(name.to_s)
|
|
25
|
+
self[name]
|
|
26
|
+
else
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def respond_to_missing?(name, ...)
|
|
32
|
+
@attributes.key?(name.to_s) || super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def inspect
|
|
36
|
+
attributes = @attributes.reject { |k, v| k[0] == "_" }.map { |k, v| "#{k}: #{v.inspect}" }
|
|
37
|
+
attributes.unshift(attributes.pop) # move id to start
|
|
38
|
+
"#<#{self.class.name} #{attributes.join(", ")}>"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
module Searchkick
|
|
2
|
+
class Index
|
|
3
|
+
attr_reader :name, :options
|
|
4
|
+
|
|
5
|
+
def initialize(name, options = {})
|
|
6
|
+
@name = name
|
|
7
|
+
@options = options
|
|
8
|
+
@klass_document_type = {} # cache
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def index_options
|
|
12
|
+
IndexOptions.new(self).index_options
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create(body = {})
|
|
16
|
+
client.indices.create index: name, body: body
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def delete
|
|
20
|
+
if alias_exists?
|
|
21
|
+
# can't call delete directly on aliases in ES 6
|
|
22
|
+
indices = client.indices.get_alias(name: name).keys
|
|
23
|
+
client.indices.delete index: indices
|
|
24
|
+
else
|
|
25
|
+
client.indices.delete index: name
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def exists?
|
|
30
|
+
client.indices.exists index: name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def refresh
|
|
34
|
+
client.indices.refresh index: name
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def alias_exists?
|
|
38
|
+
client.indices.exists_alias name: name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# call to_h for consistent results between elasticsearch gem 7 and 8
|
|
42
|
+
# could do for all API calls, but just do for ones where return value is focus for now
|
|
43
|
+
def mapping
|
|
44
|
+
client.indices.get_mapping(index: name).to_h
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# call to_h for consistent results between elasticsearch gem 7 and 8
|
|
48
|
+
def settings
|
|
49
|
+
client.indices.get_settings(index: name).to_h
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def refresh_interval
|
|
53
|
+
index_settings["refresh_interval"]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def update_settings(settings)
|
|
57
|
+
client.indices.put_settings index: name, body: settings
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def tokens(text, options = {})
|
|
61
|
+
client.indices.analyze(body: {text: text}.merge(options), index: name)["tokens"].map { |t| t["token"] }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def total_docs
|
|
65
|
+
response =
|
|
66
|
+
client.search(
|
|
67
|
+
index: name,
|
|
68
|
+
body: {
|
|
69
|
+
query: {match_all: {}},
|
|
70
|
+
size: 0,
|
|
71
|
+
track_total_hits: true
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
Results.new(nil, response).total_count
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def promote(new_name, update_refresh_interval: false)
|
|
79
|
+
if update_refresh_interval
|
|
80
|
+
new_index = Index.new(new_name, @options)
|
|
81
|
+
settings = options[:settings] || {}
|
|
82
|
+
refresh_interval = (settings[:index] && settings[:index][:refresh_interval]) || "1s"
|
|
83
|
+
new_index.update_settings(index: {refresh_interval: refresh_interval})
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
old_indices =
|
|
87
|
+
begin
|
|
88
|
+
client.indices.get_alias(name: name).keys
|
|
89
|
+
rescue => e
|
|
90
|
+
raise e unless Searchkick.not_found_error?(e)
|
|
91
|
+
{}
|
|
92
|
+
end
|
|
93
|
+
actions = old_indices.map { |old_name| {remove: {index: old_name, alias: name}} } + [{add: {index: new_name, alias: name}}]
|
|
94
|
+
client.indices.update_aliases body: {actions: actions}
|
|
95
|
+
end
|
|
96
|
+
alias_method :swap, :promote
|
|
97
|
+
|
|
98
|
+
def retrieve(record)
|
|
99
|
+
record_data = RecordData.new(self, record).record_data
|
|
100
|
+
|
|
101
|
+
# remove underscore
|
|
102
|
+
get_options = record_data.to_h { |k, v| [k.to_s.delete_prefix("_").to_sym, v] }
|
|
103
|
+
|
|
104
|
+
client.get(get_options)["_source"]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def all_indices(unaliased: false)
|
|
108
|
+
indices =
|
|
109
|
+
begin
|
|
110
|
+
if client.indices.respond_to?(:get_alias)
|
|
111
|
+
client.indices.get_alias(index: "#{name}*")
|
|
112
|
+
else
|
|
113
|
+
client.indices.get_aliases
|
|
114
|
+
end
|
|
115
|
+
rescue => e
|
|
116
|
+
raise e unless Searchkick.not_found_error?(e)
|
|
117
|
+
{}
|
|
118
|
+
end
|
|
119
|
+
indices = indices.select { |_k, v| v.empty? || v["aliases"].empty? } if unaliased
|
|
120
|
+
indices.select { |k, _v| k =~ /\A#{Regexp.escape(name)}_\d{14,17}\z/ }.keys
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# remove old indices that start w/ index_name
|
|
124
|
+
def clean_indices
|
|
125
|
+
indices = all_indices(unaliased: true)
|
|
126
|
+
indices.each do |index|
|
|
127
|
+
Index.new(index).delete
|
|
128
|
+
end
|
|
129
|
+
indices
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def store(record)
|
|
133
|
+
notify(record, "Store") do
|
|
134
|
+
queue_index([record])
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def remove(record)
|
|
139
|
+
notify(record, "Remove") do
|
|
140
|
+
queue_delete([record])
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def update_record(record, method_name)
|
|
145
|
+
notify(record, "Update") do
|
|
146
|
+
queue_update([record], method_name)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def bulk_delete(records)
|
|
151
|
+
return if records.empty?
|
|
152
|
+
|
|
153
|
+
notify_bulk(records, "Delete") do
|
|
154
|
+
queue_delete(records)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def bulk_index(records)
|
|
159
|
+
return if records.empty?
|
|
160
|
+
|
|
161
|
+
notify_bulk(records, "Import") do
|
|
162
|
+
queue_index(records)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
alias_method :import, :bulk_index
|
|
166
|
+
|
|
167
|
+
def bulk_update(records, method_name, ignore_missing: nil)
|
|
168
|
+
return if records.empty?
|
|
169
|
+
|
|
170
|
+
notify_bulk(records, "Update") do
|
|
171
|
+
queue_update(records, method_name, ignore_missing: ignore_missing)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def search_id(record)
|
|
176
|
+
RecordData.new(self, record).search_id
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def document_type(record)
|
|
180
|
+
RecordData.new(self, record).document_type
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def similar_record(record, **options)
|
|
184
|
+
options[:per_page] ||= 10
|
|
185
|
+
options[:similar] = [RecordData.new(self, record).record_data]
|
|
186
|
+
options[:models] ||= [record.class] unless options.key?(:model)
|
|
187
|
+
|
|
188
|
+
Searchkick.search("*", **options)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def reload_synonyms
|
|
192
|
+
if Searchkick.opensearch?
|
|
193
|
+
client.transport.perform_request "POST", "_plugins/_refresh_search_analyzers/#{CGI.escape(name)}"
|
|
194
|
+
else
|
|
195
|
+
begin
|
|
196
|
+
client.transport.perform_request("GET", "#{CGI.escape(name)}/_reload_search_analyzers")
|
|
197
|
+
rescue => e
|
|
198
|
+
raise Error, "Requires non-OSS version of Elasticsearch" if Searchkick.not_allowed_error?(e)
|
|
199
|
+
raise e
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# queue
|
|
205
|
+
|
|
206
|
+
def reindex_queue
|
|
207
|
+
ReindexQueue.new(name)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# reindex
|
|
211
|
+
|
|
212
|
+
# note: this is designed to be used internally
|
|
213
|
+
# so it does not check object matches index class
|
|
214
|
+
def reindex(object, method_name: nil, ignore_missing: nil, full: false, **options)
|
|
215
|
+
if @options[:job_options]
|
|
216
|
+
options[:job_options] = (@options[:job_options] || {}).merge(options[:job_options] || {})
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
if object.is_a?(Array)
|
|
220
|
+
# note: purposefully skip full
|
|
221
|
+
return reindex_records(object, method_name: method_name, ignore_missing: ignore_missing, **options)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
if !object.respond_to?(:searchkick_klass)
|
|
225
|
+
raise Error, "Cannot reindex object"
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
scoped = Searchkick.relation?(object)
|
|
229
|
+
# call searchkick_klass for inheritance
|
|
230
|
+
relation = scoped ? object.all : Searchkick.scope(object.searchkick_klass).all
|
|
231
|
+
|
|
232
|
+
refresh = options.fetch(:refresh, !scoped)
|
|
233
|
+
options.delete(:refresh)
|
|
234
|
+
|
|
235
|
+
if method_name || (scoped && !full)
|
|
236
|
+
mode = options.delete(:mode) || :inline
|
|
237
|
+
scope = options.delete(:scope)
|
|
238
|
+
job_options = options.delete(:job_options)
|
|
239
|
+
raise ArgumentError, "unsupported keywords: #{options.keys.map(&:inspect).join(", ")}" if options.any?
|
|
240
|
+
|
|
241
|
+
# import only
|
|
242
|
+
import_scope(relation, method_name: method_name, mode: mode, scope: scope, ignore_missing: ignore_missing, job_options: job_options)
|
|
243
|
+
self.refresh if refresh
|
|
244
|
+
true
|
|
245
|
+
else
|
|
246
|
+
async = options.delete(:async)
|
|
247
|
+
if async
|
|
248
|
+
if async.is_a?(Hash) && async[:wait]
|
|
249
|
+
Searchkick.warn "async option is deprecated - use mode: :async, wait: true instead"
|
|
250
|
+
options[:wait] = true unless options.key?(:wait)
|
|
251
|
+
else
|
|
252
|
+
Searchkick.warn "async option is deprecated - use mode: :async instead"
|
|
253
|
+
end
|
|
254
|
+
options[:mode] ||= :async
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
full_reindex(relation, **options)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def create_index(index_options: nil)
|
|
262
|
+
index_options ||= self.index_options
|
|
263
|
+
index = Index.new("#{name}_#{Time.now.strftime('%Y%m%d%H%M%S%L')}", @options)
|
|
264
|
+
index.create(index_options)
|
|
265
|
+
index
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def import_scope(relation, **options)
|
|
269
|
+
relation_indexer.reindex(relation, **options)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def batches_left
|
|
273
|
+
relation_indexer.batches_left
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# private
|
|
277
|
+
def klass_document_type(klass, ignore_type = false)
|
|
278
|
+
@klass_document_type[[klass, ignore_type]] ||= begin
|
|
279
|
+
if !ignore_type && klass.searchkick_klass.searchkick_options[:_type]
|
|
280
|
+
type = klass.searchkick_klass.searchkick_options[:_type]
|
|
281
|
+
type = type.call if type.respond_to?(:call)
|
|
282
|
+
type
|
|
283
|
+
else
|
|
284
|
+
klass.model_name.to_s.underscore
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# private
|
|
290
|
+
def conversions_fields
|
|
291
|
+
@conversions_fields ||= begin
|
|
292
|
+
conversions = Array(options[:conversions])
|
|
293
|
+
conversions.map(&:to_s) + conversions.map(&:to_sym)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# private
|
|
298
|
+
def conversions_v2_fields
|
|
299
|
+
@conversions_v2_fields ||= Array(options[:conversions_v2]).map(&:to_s)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# private
|
|
303
|
+
def suggest_fields
|
|
304
|
+
@suggest_fields ||= Array(options[:suggest]).map(&:to_s)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# private
|
|
308
|
+
def locations_fields
|
|
309
|
+
@locations_fields ||= begin
|
|
310
|
+
locations = Array(options[:locations])
|
|
311
|
+
locations.map(&:to_s) + locations.map(&:to_sym)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# private
|
|
316
|
+
def uuid
|
|
317
|
+
index_settings["uuid"]
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
protected
|
|
321
|
+
|
|
322
|
+
def client
|
|
323
|
+
Searchkick.client
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def queue_index(records)
|
|
327
|
+
Searchkick.indexer.queue(records.map { |r| RecordData.new(self, r).index_data })
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def queue_delete(records)
|
|
331
|
+
Searchkick.indexer.queue(records.reject { |r| r.id.blank? }.map { |r| RecordData.new(self, r).delete_data })
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def queue_update(records, method_name, ignore_missing:)
|
|
335
|
+
items = records.map { |r| RecordData.new(self, r).update_data(method_name) }
|
|
336
|
+
items.each { |i| i.instance_variable_set(:@ignore_missing, true) } if ignore_missing
|
|
337
|
+
Searchkick.indexer.queue(items)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def relation_indexer
|
|
341
|
+
@relation_indexer ||= RelationIndexer.new(self)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def index_settings
|
|
345
|
+
settings.values.first["settings"]["index"]
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def import_before_promotion(index, relation, **import_options)
|
|
349
|
+
index.import_scope(relation, **import_options)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def reindex_records(object, mode: nil, refresh: false, **options)
|
|
353
|
+
mode ||= Searchkick.callbacks_value || @options[:callbacks] || :inline
|
|
354
|
+
mode = :inline if mode == :bulk
|
|
355
|
+
|
|
356
|
+
result = RecordIndexer.new(self).reindex(object, mode: mode, full: false, **options)
|
|
357
|
+
self.refresh if refresh
|
|
358
|
+
result
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# https://gist.github.com/jarosan/3124884
|
|
362
|
+
# https://www.elastic.co/blog/changing-mapping-with-zero-downtime/
|
|
363
|
+
def full_reindex(relation, import: true, resume: false, retain: false, mode: nil, refresh_interval: nil, scope: nil, wait: nil, job_options: nil)
|
|
364
|
+
raise ArgumentError, "wait only available in :async mode" if !wait.nil? && mode != :async
|
|
365
|
+
raise ArgumentError, "Full reindex does not support :queue mode - use :async mode instead" if mode == :queue
|
|
366
|
+
|
|
367
|
+
if resume
|
|
368
|
+
index_name = all_indices.sort.last
|
|
369
|
+
raise Error, "No index to resume" unless index_name
|
|
370
|
+
index = Index.new(index_name, @options)
|
|
371
|
+
else
|
|
372
|
+
clean_indices unless retain
|
|
373
|
+
|
|
374
|
+
index_options = relation.searchkick_index_options
|
|
375
|
+
index_options.deep_merge!(settings: {index: {refresh_interval: refresh_interval}}) if refresh_interval
|
|
376
|
+
index = create_index(index_options: index_options)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
import_options = {
|
|
380
|
+
mode: (mode || :inline),
|
|
381
|
+
full: true,
|
|
382
|
+
resume: resume,
|
|
383
|
+
scope: scope,
|
|
384
|
+
job_options: job_options
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
uuid = index.uuid
|
|
388
|
+
|
|
389
|
+
# check if alias exists
|
|
390
|
+
alias_exists = alias_exists?
|
|
391
|
+
if alias_exists
|
|
392
|
+
import_before_promotion(index, relation, **import_options) if import
|
|
393
|
+
|
|
394
|
+
# get existing indices to remove
|
|
395
|
+
unless mode == :async
|
|
396
|
+
check_uuid(uuid, index.uuid)
|
|
397
|
+
promote(index.name, update_refresh_interval: !refresh_interval.nil?)
|
|
398
|
+
clean_indices unless retain
|
|
399
|
+
end
|
|
400
|
+
else
|
|
401
|
+
delete if exists?
|
|
402
|
+
promote(index.name, update_refresh_interval: !refresh_interval.nil?)
|
|
403
|
+
|
|
404
|
+
# import after promotion
|
|
405
|
+
index.import_scope(relation, **import_options) if import
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
if mode == :async
|
|
409
|
+
if wait
|
|
410
|
+
puts "Created index: #{index.name}"
|
|
411
|
+
puts "Jobs queued. Waiting..."
|
|
412
|
+
loop do
|
|
413
|
+
sleep 3
|
|
414
|
+
status = Searchkick.reindex_status(index.name)
|
|
415
|
+
break if status[:completed]
|
|
416
|
+
puts "Batches left: #{status[:batches_left]}"
|
|
417
|
+
end
|
|
418
|
+
# already promoted if alias didn't exist
|
|
419
|
+
if alias_exists
|
|
420
|
+
puts "Jobs complete. Promoting..."
|
|
421
|
+
check_uuid(uuid, index.uuid)
|
|
422
|
+
promote(index.name, update_refresh_interval: !refresh_interval.nil?)
|
|
423
|
+
end
|
|
424
|
+
clean_indices unless retain
|
|
425
|
+
puts "SUCCESS!"
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
{index_name: index.name}
|
|
429
|
+
else
|
|
430
|
+
index.refresh
|
|
431
|
+
true
|
|
432
|
+
end
|
|
433
|
+
rescue => e
|
|
434
|
+
if Searchkick.transport_error?(e) && (e.message.include?("No handler for type [text]") || e.message.include?("class java.util.ArrayList cannot be cast to class java.util.Map"))
|
|
435
|
+
raise UnsupportedVersionError
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
raise e
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# safety check
|
|
442
|
+
# still a chance for race condition since its called before promotion
|
|
443
|
+
# ideal is for user to disable automatic index creation
|
|
444
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-creation
|
|
445
|
+
def check_uuid(old_uuid, new_uuid)
|
|
446
|
+
if old_uuid != new_uuid
|
|
447
|
+
raise Error, "Safety check failed - only run one Model.reindex per model at a time"
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def notify(record, name)
|
|
452
|
+
if Searchkick.callbacks_value == :bulk
|
|
453
|
+
yield
|
|
454
|
+
else
|
|
455
|
+
name = "#{record.class.searchkick_klass.name} #{name}" if record && record.class.searchkick_klass
|
|
456
|
+
event = {
|
|
457
|
+
name: name,
|
|
458
|
+
id: search_id(record)
|
|
459
|
+
}
|
|
460
|
+
ActiveSupport::Notifications.instrument("request.searchkick", event) do
|
|
461
|
+
yield
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def notify_bulk(records, name)
|
|
467
|
+
if Searchkick.callbacks_value == :bulk
|
|
468
|
+
yield
|
|
469
|
+
else
|
|
470
|
+
event = {
|
|
471
|
+
name: "#{records.first.class.searchkick_klass.name} #{name}",
|
|
472
|
+
count: records.size
|
|
473
|
+
}
|
|
474
|
+
ActiveSupport::Notifications.instrument("request.searchkick", event) do
|
|
475
|
+
yield
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Searchkick
|
|
2
|
+
class IndexCache
|
|
3
|
+
def initialize(max_size: 20)
|
|
4
|
+
@data = {}
|
|
5
|
+
@mutex = Mutex.new
|
|
6
|
+
@max_size = max_size
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# probably a better pattern for this
|
|
10
|
+
# but keep it simple
|
|
11
|
+
def fetch(name)
|
|
12
|
+
# thread-safe in MRI without mutex
|
|
13
|
+
# due to how context switching works
|
|
14
|
+
@mutex.synchronize do
|
|
15
|
+
if @data.key?(name)
|
|
16
|
+
@data[name]
|
|
17
|
+
else
|
|
18
|
+
@data.clear if @data.size >= @max_size
|
|
19
|
+
@data[name] = yield
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clear
|
|
25
|
+
@mutex.synchronize do
|
|
26
|
+
@data.clear
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|