searchkick 4.1.1 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79cd2e4afe02598dbdc951ed222c98ab51c8f6b0580c7beb70d7f6f5e9223c8f
4
- data.tar.gz: 804ae10724df4b1f72fc93f157e185cd6acbc734952c64e1c596d20f26360ce9
3
+ metadata.gz: '0529381ceedcb4630640d4ae37b02ac2e950c3088a757946b930e56e0a8d4591'
4
+ data.tar.gz: 539ee2b8da1632f5b86ff3da063e5b815fac2ba3edb5efc2b3d2d508cdb9c002
5
5
  SHA512:
6
- metadata.gz: 732852787481b5aaf69edda3850c50846d5d07a55ba73d41dbee864988f04c5480c8c1535af6b82374982da4f95de5735f36433e6a37b46a41053ecc975fc9ef
7
- data.tar.gz: 6d4408714af66540fc2868d2d9d4bd7b72d468150f5a29c9200d4eb349ee89f3155c98536742eb0015f905de644345e832af3a752613007d424cbb2c353051c2
6
+ metadata.gz: cfb0aa73064ee937db83d948086bf5f9f1a81bc597f871288786f18ddaff1f39edf202c3a877d4147c3e963c1a736130f4250e10238444924347dbf8854b15c0
7
+ data.tar.gz: d4733742e12185d5f6296361948a20a2cc11ad14cf0f2c254cbcb80b5f5c64ba6754eb46161b22e33d786363a94b7e79db8b55d567e77ee8dc46ed3a9d7ab246
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 4.2.0 (2019-12-18)
2
+
3
+ - Added safety check for multiple `Model.reindex`
4
+ - Added `deep_paging` option
5
+ - Added request parameters to search notifications and curl representation
6
+ - Removed curl from search notifications to prevent confusion
7
+
1
8
  ## 4.1.1 (2019-11-19)
2
9
 
3
10
  - Added `chinese2` and `korean2` languages
data/README.md CHANGED
@@ -137,8 +137,6 @@ Limit / offset
137
137
  limit: 20, offset: 40
138
138
  ```
139
139
 
140
- **Note:** By default, Elasticsearch [limits pagination](#deep-pagination) to the first 10,000 results for performance
141
-
142
140
  Select
143
141
 
144
142
  ```ruby
@@ -182,6 +180,8 @@ Get the full response from Elasticsearch
182
180
  results.response
183
181
  ```
184
182
 
183
+ **Note:** By default, Elasticsearch [limits paging](#deep-paging-master) to the first 10,000 results for performance. With Elasticsearch 7, this applies to the total count as well.
184
+
185
185
  ### Boosting
186
186
 
187
187
  Boost important fields
@@ -554,7 +554,7 @@ Searchkick.callbacks(false) do
554
554
  end
555
555
  ```
556
556
 
557
- #### Associations
557
+ ### Associations
558
558
 
559
559
  Data is **not** automatically synced when an association is updated. If this is desired, add a callback to reindex:
560
560
 
@@ -1518,20 +1518,20 @@ end
1518
1518
  products.clear_scroll
1519
1519
  ```
1520
1520
 
1521
- ## Deep Pagination
1521
+ ## Deep Paging
1522
1522
 
1523
- By default, Elasticsearch limits pagination to the first 10,000 results [for performance](https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html). We don’t recommend changing this, but if you need to, you can use:
1523
+ By default, Elasticsearch limits paging to the first 10,000 results. [Here’s why](https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html). We don’t recommend changing this, but if you really need all results, you can use:
1524
1524
 
1525
1525
  ```ruby
1526
1526
  class Product < ApplicationRecord
1527
- searchkick settings: {index: {max_result_window: 1000000000}}
1527
+ searchkick deep_paging: true
1528
1528
  end
1529
1529
  ```
1530
1530
 
1531
- And search with:
1531
+ If you just need an accurate total count with Elasticsearch 7, you can instead use:
1532
1532
 
1533
1533
  ```ruby
1534
- Product.search("pears", limit: 1000000000, body_options: {track_total_hits: true})
1534
+ Product.search("pears", body_options: {track_total_hits: true})
1535
1535
  ```
1536
1536
 
1537
1537
  ## Nested Data
@@ -47,7 +47,7 @@ module Searchkick
47
47
  end
48
48
 
49
49
  def refresh_interval
50
- settings.values.first["settings"]["index"]["refresh_interval"]
50
+ index_settings["refresh_interval"]
51
51
  end
52
52
 
53
53
  def update_settings(settings)
@@ -249,6 +249,11 @@ module Searchkick
249
249
  end
250
250
  end
251
251
 
252
+ # private
253
+ def uuid
254
+ index_settings["uuid"]
255
+ end
256
+
252
257
  protected
253
258
 
254
259
  def client
@@ -259,6 +264,10 @@ module Searchkick
259
264
  @bulk_indexer ||= BulkIndexer.new(self)
260
265
  end
261
266
 
267
+ def index_settings
268
+ settings.values.first["settings"]["index"]
269
+ end
270
+
262
271
  def import_before_promotion(index, relation, **import_options)
263
272
  index.import_scope(relation, **import_options)
264
273
  end
@@ -285,6 +294,8 @@ module Searchkick
285
294
  scope: scope
286
295
  }
287
296
 
297
+ uuid = index.uuid
298
+
288
299
  # check if alias exists
289
300
  alias_exists = alias_exists?
290
301
  if alias_exists
@@ -292,6 +303,7 @@ module Searchkick
292
303
 
293
304
  # get existing indices to remove
294
305
  unless async
306
+ check_uuid(uuid, index.uuid)
295
307
  promote(index.name, update_refresh_interval: !refresh_interval.nil?)
296
308
  clean_indices unless retain
297
309
  end
@@ -316,6 +328,7 @@ module Searchkick
316
328
  # already promoted if alias didn't exist
317
329
  if alias_exists
318
330
  puts "Jobs complete. Promoting..."
331
+ check_uuid(uuid, index.uuid)
319
332
  promote(index.name, update_refresh_interval: !refresh_interval.nil?)
320
333
  end
321
334
  clean_indices unless retain
@@ -334,5 +347,15 @@ module Searchkick
334
347
 
335
348
  raise e
336
349
  end
350
+
351
+ # safety check
352
+ # still a chance for race condition since its called before promotion
353
+ # ideal is for user to disable automatic index creation
354
+ # https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-creation
355
+ def check_uuid(old_uuid, new_uuid)
356
+ if old_uuid != new_uuid
357
+ raise Searchkick::Error, "Safety check failed - only run one Model.reindex per model at a time"
358
+ end
359
+ end
337
360
  end
338
361
  end
@@ -456,6 +456,13 @@ module Searchkick
456
456
  mappings = mappings.symbolize_keys.deep_merge(custom_mapping.symbolize_keys)
457
457
  end
458
458
 
459
+ if options[:deep_paging]
460
+ if !settings.dig(:index, :max_result_window) && !settings[:"index.max_result_window"]
461
+ settings[:index] ||= {}
462
+ settings[:index][:max_result_window] = 1_000_000_000
463
+ end
464
+ end
465
+
459
466
  {
460
467
  settings: settings,
461
468
  mappings: mappings
@@ -132,7 +132,7 @@ module Searchkick
132
132
  def multi_search(searches)
133
133
  event = {
134
134
  name: "Multi Search",
135
- body: searches.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
135
+ body: searches.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join,
136
136
  }
137
137
  ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
138
138
  super
@@ -162,14 +162,17 @@ module Searchkick
162
162
 
163
163
  payload = event.payload
164
164
  name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
165
- type = payload[:query][:type]
165
+
166
166
  index = payload[:query][:index].is_a?(Array) ? payload[:query][:index].join(",") : payload[:query][:index]
167
+ type = payload[:query][:type]
168
+ request_params = payload[:query].except(:index, :type, :body)
169
+
170
+ params = []
171
+ request_params.each do |k, v|
172
+ params << "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
173
+ end
167
174
 
168
- # no easy way to tell which host the client will use
169
- host = Searchkick.client.transport.hosts.first
170
- params = ["pretty"]
171
- params << "scroll=#{payload[:query][:scroll]}" if payload[:query][:scroll]
172
- debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/#{CGI.escape(index)}#{type ? "/#{type.map { |t| CGI.escape(t) }.join(',')}" : ''}/_search?#{params.join('&')} -H 'Content-Type: application/json' -d '#{payload[:query][:body].to_json}'"
175
+ debug " #{color(name, YELLOW, true)} #{index}#{type ? "/#{type.join(',')}" : ''}/_search#{params.any? ? '?' + params.join('&') : nil} #{payload[:query][:body].to_json}"
173
176
  end
174
177
 
175
178
  def request(event)
@@ -189,9 +192,7 @@ module Searchkick
189
192
  payload = event.payload
190
193
  name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
191
194
 
192
- # no easy way to tell which host the client will use
193
- host = Searchkick.client.transport.hosts.first
194
- debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/_msearch?pretty -H 'Content-Type: application/json' -d '#{payload[:body]}'"
195
+ debug " #{color(name, YELLOW, true)} _msearch #{payload[:body]}"
195
196
  end
196
197
  end
197
198
 
@@ -3,7 +3,7 @@ module Searchkick
3
3
  def searchkick(**options)
4
4
  options = Searchkick.model_options.merge(options)
5
5
 
6
- unknown_keywords = options.keys - [:_all, :_type, :batch_size, :callbacks, :case_sensitive, :conversions, :default_fields,
6
+ unknown_keywords = options.keys - [:_all, :_type, :batch_size, :callbacks, :case_sensitive, :conversions, :deep_paging, :default_fields,
7
7
  :filterable, :geo_shape, :highlight, :ignore_above, :index_name, :index_prefix, :inheritance, :language,
8
8
  :locations, :mappings, :match, :merge_mappings, :routing, :searchable, :settings, :similarity,
9
9
  :special_characters, :stem, :stem_conversions, :suggest, :synonyms, :text_end,
@@ -106,12 +106,15 @@ module Searchkick
106
106
  query = params
107
107
  type = query[:type]
108
108
  index = query[:index].is_a?(Array) ? query[:index].join(",") : query[:index]
109
+ request_params = query.except(:index, :type, :body)
109
110
 
110
111
  # no easy way to tell which host the client will use
111
112
  host = Searchkick.client.transport.hosts.first
112
113
  credentials = host[:user] || host[:password] ? "#{host[:user]}:#{host[:password]}@" : nil
113
114
  params = ["pretty"]
114
- params << "scroll=#{options[:scroll]}" if options[:scroll]
115
+ request_params.each do |k, v|
116
+ params << "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
117
+ end
115
118
  "curl #{host[:protocol]}://#{credentials}#{host[:host]}:#{host[:port]}/#{CGI.escape(index)}#{type ? "/#{type.map { |t| CGI.escape(t) }.join(',')}" : ''}/_search?#{params.join('&')} -H 'Content-Type: application/json' -d '#{query[:body].to_json}'"
116
119
  end
117
120
 
@@ -232,7 +235,9 @@ module Searchkick
232
235
 
233
236
  # pagination
234
237
  page = [options[:page].to_i, 1].max
235
- per_page = (options[:limit] || options[:per_page] || 10_000).to_i
238
+ # maybe use index.max_result_window in the future
239
+ default_limit = searchkick_options[:deep_paging] ? 1_000_000_000 : 10_000
240
+ per_page = (options[:limit] || options[:per_page] || default_limit).to_i
236
241
  padding = [options[:padding].to_i, 0].max
237
242
  offset = options[:offset] || (page - 1) * per_page + padding
238
243
  scroll = options[:scroll]
@@ -433,19 +438,20 @@ module Searchkick
433
438
 
434
439
  models = Array(options[:models])
435
440
  if models.any? { |m| m != m.searchkick_klass }
436
- Searchkick.warn("Passing child models to models option throws off hits and pagination - use type option instead")
437
-
438
- # TODO uncomment once aliases are supported with _index
439
- # should be ES 7.5
441
+ # aliases are not supported with _index in ES below 7.5
440
442
  # see https://github.com/elastic/elasticsearch/pull/46640
441
- # index_type_or =
442
- # models.map do |m|
443
- # v = {_index: m.searchkick_index.name}
444
- # v[:type] = m.searchkick_index.klass_document_type(m, true) if m != m.searchkick_klass
445
- # v
446
- # end
447
-
448
- # where[:or] = Array(where[:or]) + [index_type_or]
443
+ if below75?
444
+ Searchkick.warn("Passing child models to models option throws off hits and pagination - use type option instead")
445
+ else
446
+ index_type_or =
447
+ models.map do |m|
448
+ v = {_index: m.searchkick_index.name}
449
+ v[:type] = m.searchkick_index.klass_document_type(m, true) if m != m.searchkick_klass
450
+ v
451
+ end
452
+
453
+ where[:or] = Array(where[:or]) + [index_type_or]
454
+ end
449
455
  end
450
456
 
451
457
  # start everything as efficient filters
@@ -516,6 +522,10 @@ module Searchkick
516
522
  # routing
517
523
  @routing = options[:routing] if options[:routing]
518
524
 
525
+ if searchkick_options[:deep_paging] && !below70?
526
+ payload[:track_total_hits] = true
527
+ end
528
+
519
529
  # merge more body options
520
530
  payload = payload.deep_merge(options[:body_options]) if options[:body_options]
521
531
 
@@ -1097,5 +1107,9 @@ module Searchkick
1097
1107
  def below70?
1098
1108
  Searchkick.server_below?("7.0.0")
1099
1109
  end
1110
+
1111
+ def below75?
1112
+ Searchkick.server_below?("7.5.0")
1113
+ end
1100
1114
  end
1101
1115
  end
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "4.1.1"
2
+ VERSION = "4.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchkick
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.1
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-20 00:00:00.000000000 Z
11
+ date: 2019-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -145,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
145
  - !ruby/object:Gem::Version
146
146
  version: '0'
147
147
  requirements: []
148
- rubygems_version: 3.0.6
148
+ rubygems_version: 3.0.3
149
149
  signing_key:
150
150
  specification_version: 4
151
151
  summary: Intelligent search made easy with Rails and Elasticsearch