Commit 4c2d92bd534aedcad94d2e0fa45e7252ee709917

Authored by rainsense
2 parents 71626568 3dc4776b

Merge upstream

@@ -33,6 +33,7 @@ matrix: @@ -33,6 +33,7 @@ matrix:
33 - gemfile: Gemfile 33 - gemfile: Gemfile
34 env: ELASTICSEARCH_VERSION=5.0.1 34 env: ELASTICSEARCH_VERSION=5.0.1
35 jdk: oraclejdk8 35 jdk: oraclejdk8
  36 + allow_failures:
36 - gemfile: Gemfile 37 - gemfile: Gemfile
37 - env: ELASTICSEARCH_VERSION=6.0.0-alpha2 38 + env: ELASTICSEARCH_VERSION=6.0.0-beta1
38 jdk: oraclejdk8 39 jdk: oraclejdk8
@@ -2,6 +2,8 @@ @@ -2,6 +2,8 @@
2 2
3 - Added `_all` and `default_fields` options 3 - Added `_all` and `default_fields` options
4 - Added global `index_prefix` option 4 - Added global `index_prefix` option
  5 +- Added `wait` option to async reindex
  6 +- Raise error for `reindex_status` when Redis not configured
5 7
6 ## 2.3.1 8 ## 2.3.1
7 9
@@ -1214,6 +1214,12 @@ And use: @@ -1214,6 +1214,12 @@ And use:
1214 Searchkick.reindex_status(index_name) 1214 Searchkick.reindex_status(index_name)
1215 ``` 1215 ```
1216 1216
  1217 +You can also have Searchkick wait for reindexing to complete [master]
  1218 +
  1219 +```ruby
  1220 +Searchkick.reindex(async: {wait: true})
  1221 +```
  1222 +
1217 You can use [ActiveJob::TrafficControl](https://github.com/nickelser/activejob-traffic_control) to control concurrency. Install the gem: 1223 You can use [ActiveJob::TrafficControl](https://github.com/nickelser/activejob-traffic_control) to control concurrency. Install the gem:
1218 1224
1219 ```ruby 1225 ```ruby
lib/searchkick.rb
@@ -8,6 +8,7 @@ require "searchkick/indexer" @@ -8,6 +8,7 @@ require "searchkick/indexer"
8 require "searchkick/reindex_queue" 8 require "searchkick/reindex_queue"
9 require "searchkick/results" 9 require "searchkick/results"
10 require "searchkick/query" 10 require "searchkick/query"
  11 +require "searchkick/multi_search"
11 require "searchkick/model" 12 require "searchkick/model"
12 require "searchkick/tasks" 13 require "searchkick/tasks"
13 require "searchkick/middleware" 14 require "searchkick/middleware"
@@ -101,14 +102,8 @@ module Searchkick @@ -101,14 +102,8 @@ module Searchkick
101 end 102 end
102 end 103 end
103 104
104 - def self.multi_search(queries)  
105 - if queries.any?  
106 - responses = client.msearch(body: queries.flat_map { |q| [q.params.except(:body), q.body] })["responses"]  
107 - queries.each_with_index do |query, i|  
108 - query.handle_response(responses[i])  
109 - end  
110 - end  
111 - queries 105 + def self.multi_search(queries, retry_misspellings: false)
  106 + Searchkick::MultiSearch.new(queries, retry_misspellings: retry_misspellings).perform
112 end 107 end
113 108
114 # callbacks 109 # callbacks
@@ -153,6 +148,8 @@ module Searchkick @@ -153,6 +148,8 @@ module Searchkick
153 completed: batches_left == 0, 148 completed: batches_left == 0,
154 batches_left: batches_left 149 batches_left: batches_left
155 } 150 }
  151 + else
  152 + raise Searchkick::Error, "Redis not configured"
156 end 153 end
157 end 154 end
158 155
lib/searchkick/index.rb
@@ -15,7 +15,13 @@ module Searchkick @@ -15,7 +15,13 @@ module Searchkick
15 end 15 end
16 16
17 def delete 17 def delete
18 - client.indices.delete index: name 18 + if !Searchkick.server_below?("6.0.0-alpha1") && alias_exists?
  19 + # can't call delete directly on aliases in ES 6
  20 + indices = client.indices.get_alias(name: name).keys
  21 + client.indices.delete index: indices
  22 + else
  23 + client.indices.delete index: name
  24 + end
19 end 25 end
20 26
21 def exists? 27 def exists?
@@ -228,7 +234,8 @@ module Searchkick @@ -228,7 +234,8 @@ module Searchkick
228 end 234 end
229 235
230 # check if alias exists 236 # check if alias exists
231 - if alias_exists? 237 + alias_exists = alias_exists?
  238 + if alias_exists
232 # import before promotion 239 # import before promotion
233 index.import_scope(scope, resume: resume, async: async, full: true) if import 240 index.import_scope(scope, resume: resume, async: async, full: true) if import
234 241
@@ -246,6 +253,24 @@ module Searchkick @@ -246,6 +253,24 @@ module Searchkick
246 end 253 end
247 254
248 if async 255 if async
  256 + if async.is_a?(Hash) && async[:wait]
  257 + puts "Created index: #{index.name}"
  258 + puts "Jobs queued. Waiting..."
  259 + loop do
  260 + sleep 3
  261 + status = Searchkick.reindex_status(index.name)
  262 + break if status[:completed]
  263 + puts "Batches left: #{status[:batches_left]}"
  264 + end
  265 + # already promoted if alias didn't exist
  266 + if alias_exists
  267 + puts "Jobs complete. Promoting..."
  268 + promote(index.name, update_refresh_interval: !refresh_interval.nil?)
  269 + end
  270 + clean_indices unless retain
  271 + puts "SUCCESS!"
  272 + end
  273 +
249 {index_name: index.name} 274 {index_name: index.name}
250 else 275 else
251 index.refresh 276 index.refresh
lib/searchkick/index_options.rb
@@ -162,11 +162,6 @@ module Searchkick @@ -162,11 +162,6 @@ module Searchkick
162 settings[:similarity] = {default: {type: options[:similarity]}} 162 settings[:similarity] = {default: {type: options[:similarity]}}
163 end 163 end
164 164
165 - unless below60  
166 - settings[:mapping] ||= {}  
167 - settings[:mapping][:single_type] = false  
168 - end  
169 -  
170 settings.deep_merge!(options[:settings] || {}) 165 settings.deep_merge!(options[:settings] || {})
171 166
172 # synonyms 167 # synonyms
@@ -188,7 +183,7 @@ module Searchkick @@ -188,7 +183,7 @@ module Searchkick
188 # - Only apply the synonym expansion at index time 183 # - Only apply the synonym expansion at index time
189 # - Don't have the synonym filter applied search 184 # - Don't have the synonym filter applied search
190 # - Use directional synonyms where appropriate. You want to make sure that you're not injecting terms that are too general. 185 # - Use directional synonyms where appropriate. You want to make sure that you're not injecting terms that are too general.
191 - settings[:analysis][:analyzer][default_analyzer][:filter].insert(4, "searchkick_synonym") 186 + settings[:analysis][:analyzer][default_analyzer][:filter].insert(4, "searchkick_synonym") if below60
192 settings[:analysis][:analyzer][default_analyzer][:filter] << "searchkick_synonym" 187 settings[:analysis][:analyzer][default_analyzer][:filter] << "searchkick_synonym"
193 188
194 %w(word_start word_middle word_end).each do |type| 189 %w(word_start word_middle word_end).each do |type|
lib/searchkick/logging.rb
@@ -129,7 +129,7 @@ module Searchkick @@ -129,7 +129,7 @@ module Searchkick
129 end 129 end
130 130
131 module SearchkickWithInstrumentation 131 module SearchkickWithInstrumentation
132 - def multi_search(searches) 132 + def multi_search(searches, **options)
133 event = { 133 event = {
134 name: "Multi Search", 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
lib/searchkick/multi_search.rb 0 โ†’ 100644
@@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
  1 +module Searchkick
  2 + class MultiSearch
  3 + attr_reader :queries
  4 +
  5 + def initialize(queries, retry_misspellings: false)
  6 + @queries = queries
  7 + @retry_misspellings = retry_misspellings
  8 + end
  9 +
  10 + def perform
  11 + if queries.any?
  12 + perform_search(queries, retry_misspellings: @retry_misspellings)
  13 + end
  14 + end
  15 +
  16 + private
  17 +
  18 + def perform_search(queries, retry_misspellings: true)
  19 + responses = client.msearch(body: queries.flat_map { |q| [q.params.except(:body), q.body] })["responses"]
  20 +
  21 + retry_queries = []
  22 + queries.each_with_index do |query, i|
  23 + if retry_misspellings && query.retry_misspellings?(responses[i])
  24 + query.send(:prepare) # okay, since we don't want to expose this method outside Searchkick
  25 + retry_queries << query
  26 + else
  27 + query.handle_response(responses[i])
  28 + end
  29 + end
  30 +
  31 + if retry_misspellings && retry_queries.any?
  32 + perform_search(retry_queries, retry_misspellings: false)
  33 + end
  34 +
  35 + queries
  36 + end
  37 +
  38 + def client
  39 + Searchkick.client
  40 + end
  41 + end
  42 +end
lib/searchkick/query.rb
@@ -79,7 +79,7 @@ module Searchkick @@ -79,7 +79,7 @@ module Searchkick
79 @execute ||= begin 79 @execute ||= begin
80 begin 80 begin
81 response = execute_search 81 response = execute_search
82 - if @misspellings_below && response["hits"]["total"] < @misspellings_below 82 + if retry_misspellings?(response)
83 prepare 83 prepare
84 response = execute_search 84 response = execute_search
85 end 85 end
@@ -160,6 +160,10 @@ module Searchkick @@ -160,6 +160,10 @@ module Searchkick
160 @execute = Searchkick::Results.new(searchkick_klass, response, opts) 160 @execute = Searchkick::Results.new(searchkick_klass, response, opts)
161 end 161 end
162 162
  163 + def retry_misspellings?(response)
  164 + @misspellings_below && response["hits"]["total"] < @misspellings_below
  165 + end
  166 +
163 private 167 private
164 168
165 def handle_error(e) 169 def handle_error(e)
test/highlight_test.rb
@@ -32,7 +32,7 @@ class HighlightTest &lt; Minitest::Test @@ -32,7 +32,7 @@ class HighlightTest &lt; Minitest::Test
32 32
33 def test_field_options 33 def test_field_options
34 store_names ["Two Door Cinema Club are a Northern Irish indie rock band"] 34 store_names ["Two Door Cinema Club are a Northern Irish indie rock band"]
35 - fragment_size = ENV["MATCH"] == "word_start" ? 26 : 20 35 + fragment_size = ENV["MATCH"] == "word_start" ? 26 : 21
36 assert_equal "Two Door <em>Cinema</em> Club are", Product.search("cinema", fields: [:name], highlight: {fields: {name: {fragment_size: fragment_size}}}).first.search_highlights[:name] 36 assert_equal "Two Door <em>Cinema</em> Club are", Product.search("cinema", fields: [:name], highlight: {fields: {name: {fragment_size: fragment_size}}}).first.search_highlights[:name]
37 end 37 end
38 38
test/multi_search_test.rb
@@ -19,4 +19,18 @@ class MultiSearchTest &lt; Minitest::Test @@ -19,4 +19,18 @@ class MultiSearchTest &lt; Minitest::Test
19 assert !products.error 19 assert !products.error
20 assert stores.error 20 assert stores.error
21 end 21 end
  22 +
  23 + def test_misspellings_below_unmet
  24 + store_names ["abc", "abd", "aee"]
  25 + products = Product.search("abc", misspellings: {below: 2}, execute: false)
  26 + Searchkick.multi_search([products])
  27 + assert_equal ["abc"], products.map(&:name)
  28 + end
  29 +
  30 + def test_misspellings_below_unmet_retry
  31 + store_names ["abc", "abd", "aee"]
  32 + products = Product.search("abc", misspellings: {below: 2}, execute: false)
  33 + Searchkick.multi_search([products], retry_misspellings: true)
  34 + assert_equal ["abc", "abd"], products.map(&:name)
  35 + end
22 end 36 end