searchkick 5.5.2 → 6.0.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/CHANGELOG.md +21 -0
- data/README.md +249 -209
- data/lib/searchkick/bulk_reindex_job.rb +3 -5
- data/lib/searchkick/hash_wrapper.rb +30 -9
- data/lib/searchkick/index.rb +25 -16
- data/lib/searchkick/index_options.rb +34 -18
- data/lib/searchkick/indexer.rb +10 -2
- data/lib/searchkick/log_subscriber.rb +1 -1
- data/lib/searchkick/model.rb +18 -8
- data/lib/searchkick/multi_search.rb +7 -2
- data/lib/searchkick/process_batch_job.rb +2 -2
- data/lib/searchkick/process_queue_job.rb +4 -3
- data/lib/searchkick/query.rb +90 -100
- data/lib/searchkick/record_data.rb +19 -0
- data/lib/searchkick/record_indexer.rb +20 -10
- data/lib/searchkick/reindex_queue.rb +1 -24
- data/lib/searchkick/reindex_v2_job.rb +3 -3
- data/lib/searchkick/relation.rb +504 -72
- data/lib/searchkick/relation_indexer.rb +39 -10
- data/lib/searchkick/results.rb +14 -9
- data/lib/searchkick/version.rb +1 -1
- data/lib/searchkick.rb +12 -31
- metadata +4 -18
@@ -6,7 +6,7 @@ module Searchkick
|
|
6
6
|
@index = index
|
7
7
|
end
|
8
8
|
|
9
|
-
def reindex(relation, mode:, method_name: nil, full: false, resume: false, scope: nil)
|
9
|
+
def reindex(relation, mode:, method_name: nil, ignore_missing: nil, full: false, resume: false, scope: nil, job_options: nil)
|
10
10
|
# apply scopes
|
11
11
|
if scope
|
12
12
|
relation = relation.send(scope)
|
@@ -29,7 +29,7 @@ module Searchkick
|
|
29
29
|
end
|
30
30
|
|
31
31
|
if mode == :async && full
|
32
|
-
return full_reindex_async(relation)
|
32
|
+
return full_reindex_async(relation, job_options: job_options)
|
33
33
|
end
|
34
34
|
|
35
35
|
relation = resume_relation(relation) if resume
|
@@ -37,7 +37,9 @@ module Searchkick
|
|
37
37
|
reindex_options = {
|
38
38
|
mode: mode,
|
39
39
|
method_name: method_name,
|
40
|
-
full: full
|
40
|
+
full: full,
|
41
|
+
ignore_missing: ignore_missing,
|
42
|
+
job_options: job_options
|
41
43
|
}
|
42
44
|
record_indexer = RecordIndexer.new(index)
|
43
45
|
|
@@ -128,23 +130,50 @@ module Searchkick
|
|
128
130
|
@batch_size ||= index.options[:batch_size] || 1000
|
129
131
|
end
|
130
132
|
|
131
|
-
def full_reindex_async(relation)
|
133
|
+
def full_reindex_async(relation, job_options: nil)
|
132
134
|
batch_id = 1
|
133
135
|
class_name = relation.searchkick_options[:class_name]
|
136
|
+
starting_id = false
|
134
137
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
+
if relation.respond_to?(:primary_key)
|
139
|
+
primary_key = relation.primary_key
|
140
|
+
|
141
|
+
starting_id =
|
142
|
+
begin
|
143
|
+
relation.minimum(primary_key)
|
144
|
+
rescue ActiveRecord::StatementInvalid
|
145
|
+
false
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
if starting_id.nil?
|
150
|
+
# no records, do nothing
|
151
|
+
elsif starting_id.is_a?(Numeric)
|
152
|
+
max_id = relation.maximum(primary_key)
|
153
|
+
batches_count = ((max_id - starting_id + 1) / batch_size.to_f).ceil
|
154
|
+
|
155
|
+
batches_count.times do |i|
|
156
|
+
min_id = starting_id + (i * batch_size)
|
157
|
+
batch_job(class_name, batch_id, job_options, min_id: min_id, max_id: min_id + batch_size - 1)
|
158
|
+
batch_id += 1
|
159
|
+
end
|
160
|
+
else
|
161
|
+
in_batches(relation) do |items|
|
162
|
+
batch_job(class_name, batch_id, job_options, record_ids: items.map(&:id).map { |v| v.instance_of?(Integer) ? v : v.to_s })
|
163
|
+
batch_id += 1
|
164
|
+
end
|
138
165
|
end
|
139
166
|
end
|
140
167
|
|
141
|
-
def batch_job(class_name, batch_id,
|
168
|
+
def batch_job(class_name, batch_id, job_options, **options)
|
169
|
+
job_options ||= {}
|
170
|
+
# TODO expire Redis key
|
142
171
|
Searchkick.with_redis { |r| r.call("SADD", batches_key, [batch_id]) }
|
143
|
-
Searchkick::BulkReindexJob.perform_later(
|
172
|
+
Searchkick::BulkReindexJob.set(**job_options).perform_later(
|
144
173
|
class_name: class_name,
|
145
174
|
index_name: index.name,
|
146
175
|
batch_id: batch_id,
|
147
|
-
|
176
|
+
**options
|
148
177
|
)
|
149
178
|
end
|
150
179
|
|
data/lib/searchkick/results.rb
CHANGED
@@ -3,8 +3,7 @@ module Searchkick
|
|
3
3
|
include Enumerable
|
4
4
|
extend Forwardable
|
5
5
|
|
6
|
-
|
7
|
-
attr_reader :klass, :response, :options
|
6
|
+
attr_reader :response
|
8
7
|
|
9
8
|
def_delegators :results, :each, :any?, :empty?, :size, :length, :slice, :[], :to_ary
|
10
9
|
|
@@ -14,11 +13,6 @@ module Searchkick
|
|
14
13
|
@options = options
|
15
14
|
end
|
16
15
|
|
17
|
-
# TODO make private in 6.0
|
18
|
-
def results
|
19
|
-
@results ||= with_hit.map(&:first)
|
20
|
-
end
|
21
|
-
|
22
16
|
def with_hit
|
23
17
|
return enum_for(:with_hit) unless block_given?
|
24
18
|
|
@@ -34,7 +28,7 @@ module Searchkick
|
|
34
28
|
def suggestions
|
35
29
|
if response["suggest"]
|
36
30
|
response["suggest"].values.flat_map { |v| v.first["options"] }.sort_by { |o| -o["score"] }.map { |o| o["text"] }.uniq
|
37
|
-
elsif options[:suggest]
|
31
|
+
elsif options[:suggest]
|
38
32
|
[]
|
39
33
|
else
|
40
34
|
raise "Pass `suggest: true` to the search method for suggestions"
|
@@ -193,7 +187,12 @@ module Searchkick
|
|
193
187
|
else
|
194
188
|
begin
|
195
189
|
# TODO Active Support notifications for this scroll call
|
196
|
-
|
190
|
+
params = {
|
191
|
+
scroll: options[:scroll],
|
192
|
+
body: {scroll_id: scroll_id}
|
193
|
+
}
|
194
|
+
params[:opaque_id] = options[:opaque_id] if options[:opaque_id]
|
195
|
+
Results.new(@klass, Searchkick.client.scroll(params), @options)
|
197
196
|
rescue => e
|
198
197
|
if Searchkick.not_found_error?(e) && e.message =~ /search_context_missing_exception/i
|
199
198
|
raise Error, "Scroll id has expired"
|
@@ -217,6 +216,12 @@ module Searchkick
|
|
217
216
|
|
218
217
|
private
|
219
218
|
|
219
|
+
attr_reader :klass, :options
|
220
|
+
|
221
|
+
def results
|
222
|
+
@results ||= with_hit.map(&:first)
|
223
|
+
end
|
224
|
+
|
220
225
|
def with_hit_and_missing_records
|
221
226
|
@with_hit_and_missing_records ||= begin
|
222
227
|
missing_records = []
|
data/lib/searchkick/version.rb
CHANGED
data/lib/searchkick.rb
CHANGED
@@ -3,8 +3,9 @@ require "active_support"
|
|
3
3
|
require "active_support/core_ext/hash/deep_merge"
|
4
4
|
require "active_support/core_ext/module/attr_internal"
|
5
5
|
require "active_support/core_ext/module/delegation"
|
6
|
+
require "active_support/deprecation"
|
7
|
+
require "active_support/log_subscriber"
|
6
8
|
require "active_support/notifications"
|
7
|
-
require "hashie"
|
8
9
|
|
9
10
|
# stdlib
|
10
11
|
require "forwardable"
|
@@ -49,7 +50,7 @@ module Searchkick
|
|
49
50
|
class MissingIndexError < Error; end
|
50
51
|
class UnsupportedVersionError < Error
|
51
52
|
def message
|
52
|
-
"This version of Searchkick requires Elasticsearch
|
53
|
+
"This version of Searchkick requires Elasticsearch 8+ or OpenSearch 2+"
|
53
54
|
end
|
54
55
|
end
|
55
56
|
class InvalidQueryError < Error; end
|
@@ -57,7 +58,7 @@ module Searchkick
|
|
57
58
|
class ImportError < Error; end
|
58
59
|
|
59
60
|
class << self
|
60
|
-
attr_accessor :search_method_name, :timeout, :models, :client_options, :redis, :index_prefix, :index_suffix, :queue_name, :model_options, :client_type
|
61
|
+
attr_accessor :search_method_name, :timeout, :models, :client_options, :redis, :index_prefix, :index_suffix, :queue_name, :model_options, :client_type, :parent_job
|
61
62
|
attr_writer :client, :env, :search_timeout
|
62
63
|
attr_reader :aws_credentials
|
63
64
|
end
|
@@ -67,6 +68,7 @@ module Searchkick
|
|
67
68
|
self.client_options = {}
|
68
69
|
self.queue_name = :searchkick
|
69
70
|
self.model_options = {}
|
71
|
+
self.parent_job = "ActiveJob::Base"
|
70
72
|
|
71
73
|
def self.client
|
72
74
|
@client ||= begin
|
@@ -83,34 +85,21 @@ module Searchkick
|
|
83
85
|
raise Error, "No client found - install the `elasticsearch` or `opensearch-ruby` gem"
|
84
86
|
end
|
85
87
|
|
86
|
-
# check after client to ensure faraday is installed
|
87
|
-
# TODO remove in Searchkick 6
|
88
|
-
if defined?(Typhoeus) && Gem::Version.new(Faraday::VERSION) < Gem::Version.new("0.14.0")
|
89
|
-
require "typhoeus/adapters/faraday"
|
90
|
-
end
|
91
|
-
|
92
88
|
if client_type == :opensearch
|
93
89
|
OpenSearch::Client.new({
|
94
90
|
url: ENV["OPENSEARCH_URL"],
|
95
|
-
|
96
|
-
transport_options: {request: {timeout: timeout}, headers: {content_type: "application/json"}},
|
91
|
+
transport_options: {request: {timeout: timeout}},
|
97
92
|
retry_on_failure: 2
|
98
93
|
}.deep_merge(client_options)) do |f|
|
99
94
|
f.use Searchkick::Middleware
|
100
95
|
f.request :aws_sigv4, signer_middleware_aws_params if aws_credentials
|
101
96
|
end
|
102
97
|
else
|
103
|
-
raise Error, "The `elasticsearch` gem must be
|
104
|
-
|
105
|
-
transport_options = {request: {timeout: timeout}}
|
106
|
-
# TODO remove headers in Searchkick 6
|
107
|
-
if Elasticsearch::VERSION.to_i < 9
|
108
|
-
transport_options[:headers] = {content_type: "application/json"}
|
109
|
-
end
|
98
|
+
raise Error, "The `elasticsearch` gem must be 8+" if Elasticsearch::VERSION.to_i < 8
|
110
99
|
|
111
100
|
Elasticsearch::Client.new({
|
112
101
|
url: ENV["ELASTICSEARCH_URL"],
|
113
|
-
transport_options:
|
102
|
+
transport_options: {request: {timeout: timeout}},
|
114
103
|
retry_on_failure: 2
|
115
104
|
}.deep_merge(client_options)) do |f|
|
116
105
|
f.use Searchkick::Middleware
|
@@ -144,16 +133,14 @@ module Searchkick
|
|
144
133
|
@opensearch
|
145
134
|
end
|
146
135
|
|
147
|
-
|
148
|
-
def self.server_below?(version, true_version = false)
|
149
|
-
server_version = !true_version && opensearch? ? "7.10.2" : self.server_version
|
136
|
+
def self.server_below?(version)
|
150
137
|
Gem::Version.new(server_version.split("-")[0]) < Gem::Version.new(version.split("-")[0])
|
151
138
|
end
|
152
139
|
|
153
140
|
# private
|
154
141
|
def self.knn_support?
|
155
142
|
if opensearch?
|
156
|
-
!server_below?("2.4.0"
|
143
|
+
!server_below?("2.4.0")
|
157
144
|
else
|
158
145
|
!server_below?("8.6.0")
|
159
146
|
end
|
@@ -184,17 +171,11 @@ module Searchkick
|
|
184
171
|
end
|
185
172
|
end
|
186
173
|
|
187
|
-
# TODO remove in Searchkick 6
|
188
|
-
if options[:execute] == false
|
189
|
-
Searchkick.warn("The execute option is no longer needed")
|
190
|
-
options.delete(:execute)
|
191
|
-
end
|
192
|
-
|
193
174
|
options = options.merge(block: block) if block
|
194
175
|
Relation.new(klass, term, **options)
|
195
176
|
end
|
196
177
|
|
197
|
-
def self.multi_search(queries)
|
178
|
+
def self.multi_search(queries, opaque_id: nil)
|
198
179
|
return if queries.empty?
|
199
180
|
|
200
181
|
queries = queries.map { |q| q.send(:query) }
|
@@ -203,7 +184,7 @@ module Searchkick
|
|
203
184
|
body: queries.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
|
204
185
|
}
|
205
186
|
ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
|
206
|
-
MultiSearch.new(queries).perform
|
187
|
+
MultiSearch.new(queries, opaque_id: opaque_id).perform
|
207
188
|
end
|
208
189
|
end
|
209
190
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: searchkick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 6.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
@@ -15,28 +15,14 @@ dependencies:
|
|
15
15
|
requirements:
|
16
16
|
- - ">="
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: '7.
|
18
|
+
version: '7.2'
|
19
19
|
type: :runtime
|
20
20
|
prerelease: false
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
22
22
|
requirements:
|
23
23
|
- - ">="
|
24
24
|
- !ruby/object:Gem::Version
|
25
|
-
version: '7.
|
26
|
-
- !ruby/object:Gem::Dependency
|
27
|
-
name: hashie
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
29
|
-
requirements:
|
30
|
-
- - ">="
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '0'
|
33
|
-
type: :runtime
|
34
|
-
prerelease: false
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - ">="
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '0'
|
25
|
+
version: '7.2'
|
40
26
|
email: andrew@ankane.org
|
41
27
|
executables: []
|
42
28
|
extensions: []
|
@@ -91,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
77
|
- !ruby/object:Gem::Version
|
92
78
|
version: '0'
|
93
79
|
requirements: []
|
94
|
-
rubygems_version: 3.6.
|
80
|
+
rubygems_version: 3.6.9
|
95
81
|
specification_version: 4
|
96
82
|
summary: Intelligent search made easy with Rails and Elasticsearch or OpenSearch
|
97
83
|
test_files: []
|