searchkick 4.6.3 → 5.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # thread-local (technically fiber-local) indexer
2
+ # used to aggregate bulk callbacks across models
1
3
  module Searchkick
2
4
  class Indexer
3
5
  attr_reader :queued_items
@@ -14,15 +16,20 @@ module Searchkick
14
16
  def perform
15
17
  items = @queued_items
16
18
  @queued_items = []
17
- if items.any?
18
- response = Searchkick.client.bulk(body: items)
19
- if response["errors"]
20
- first_with_error = response["items"].map do |item|
21
- (item["index"] || item["delete"] || item["update"])
22
- end.find { |item| item["error"] }
23
- raise Searchkick::ImportError, "#{first_with_error["error"]} on item with id '#{first_with_error["_id"]}'"
24
- end
19
+
20
+ return if items.empty?
21
+
22
+ response = Searchkick.client.bulk(body: items)
23
+ if response["errors"]
24
+ # note: delete does not set error when item not found
25
+ first_with_error = response["items"].map do |item|
26
+ (item["index"] || item["delete"] || item["update"])
27
+ end.find { |item| item["error"] }
28
+ raise ImportError, "#{first_with_error["error"]} on item with id '#{first_with_error["_id"]}'"
25
29
  end
30
+
31
+ # maybe return response in future
32
+ nil
26
33
  end
27
34
  end
28
35
  end
@@ -0,0 +1,57 @@
1
+ # based on https://gist.github.com/mnutt/566725
2
+ module Searchkick
3
+ class LogSubscriber < ActiveSupport::LogSubscriber
4
+ def self.runtime=(value)
5
+ Thread.current[:searchkick_runtime] = value
6
+ end
7
+
8
+ def self.runtime
9
+ Thread.current[:searchkick_runtime] ||= 0
10
+ end
11
+
12
+ def self.reset_runtime
13
+ rt = runtime
14
+ self.runtime = 0
15
+ rt
16
+ end
17
+
18
+ def search(event)
19
+ self.class.runtime += event.duration
20
+ return unless logger.debug?
21
+
22
+ payload = event.payload
23
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
24
+
25
+ index = payload[:query][:index].is_a?(Array) ? payload[:query][:index].join(",") : payload[:query][:index]
26
+ type = payload[:query][:type]
27
+ request_params = payload[:query].except(:index, :type, :body)
28
+
29
+ params = []
30
+ request_params.each do |k, v|
31
+ params << "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
32
+ end
33
+
34
+ debug " #{color(name, YELLOW, bold: true)} #{index}#{type ? "/#{type.join(',')}" : ''}/_search#{params.any? ? '?' + params.join('&') : nil} #{payload[:query][:body].to_json}"
35
+ end
36
+
37
+ def request(event)
38
+ self.class.runtime += event.duration
39
+ return unless logger.debug?
40
+
41
+ payload = event.payload
42
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
43
+
44
+ debug " #{color(name, YELLOW, bold: true)} #{payload.except(:name).to_json}"
45
+ end
46
+
47
+ def multi_search(event)
48
+ self.class.runtime += event.duration
49
+ return unless logger.debug?
50
+
51
+ payload = event.payload
52
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
53
+
54
+ debug " #{color(name, YELLOW, bold: true)} _msearch #{payload[:body]}"
55
+ end
56
+ end
57
+ end
@@ -1,10 +1,17 @@
1
- require "faraday/middleware"
1
+ require "faraday"
2
2
 
3
3
  module Searchkick
4
4
  class Middleware < Faraday::Middleware
5
5
  def call(env)
6
- if env[:method] == :get && env[:url].path.to_s.end_with?("/_search")
6
+ path = env[:url].path.to_s
7
+ if path.end_with?("/_search")
7
8
  env[:request][:timeout] = Searchkick.search_timeout
9
+ elsif path.end_with?("/_msearch")
10
+ # assume no concurrent searches for timeout for now
11
+ searches = env[:request_body].count("\n") / 2
12
+ # do not allow timeout to exceed Searchkick.timeout
13
+ timeout = [Searchkick.search_timeout * searches, Searchkick.timeout].min
14
+ env[:request][:timeout] = timeout
8
15
  end
9
16
  @app.call(env)
10
17
  end
@@ -5,9 +5,9 @@ module Searchkick
5
5
 
6
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
- :locations, :mappings, :match, :merge_mappings, :routing, :searchable, :search_synonyms, :settings, :similarity,
8
+ :locations, :mappings, :match, :max_result_window, :merge_mappings, :routing, :searchable, :search_synonyms, :settings, :similarity,
9
9
  :special_characters, :stem, :stemmer, :stem_conversions, :stem_exclusion, :stemmer_override, :suggest, :synonyms, :text_end,
10
- :text_middle, :text_start, :word, :wordnet, :word_end, :word_middle, :word_start]
10
+ :text_middle, :text_start, :unscope, :word, :word_end, :word_middle, :word_start]
11
11
  raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
12
12
 
13
13
  raise "Only call searchkick once per model" if respond_to?(:searchkick_index)
@@ -22,52 +22,78 @@ module Searchkick
22
22
  raise ArgumentError, "Invalid value for callbacks"
23
23
  end
24
24
 
25
- index_name =
26
- if options[:index_name]
27
- options[:index_name]
28
- elsif options[:index_prefix].respond_to?(:call)
29
- -> { [options[:index_prefix].call, model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_") }
30
- else
31
- [options.key?(:index_prefix) ? options[:index_prefix] : Searchkick.index_prefix, model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_")
32
- end
25
+ base = self
26
+
27
+ mod = Module.new
28
+ include(mod)
29
+ mod.module_eval do
30
+ def reindex(method_name = nil, mode: nil, refresh: false)
31
+ self.class.searchkick_index.reindex([self], method_name: method_name, mode: mode, refresh: refresh, single: true)
32
+ end unless base.method_defined?(:reindex)
33
+
34
+ def similar(**options)
35
+ self.class.searchkick_index.similar_record(self, **options)
36
+ end unless base.method_defined?(:similar)
37
+
38
+ def search_data
39
+ data = respond_to?(:to_hash) ? to_hash : serializable_hash
40
+ data.delete("id")
41
+ data.delete("_id")
42
+ data.delete("_type")
43
+ data
44
+ end unless base.method_defined?(:search_data)
45
+
46
+ def should_index?
47
+ true
48
+ end unless base.method_defined?(:should_index?)
49
+ end
33
50
 
34
51
  class_eval do
35
- cattr_reader :searchkick_options, :searchkick_klass
52
+ cattr_reader :searchkick_options, :searchkick_klass, instance_reader: false
36
53
 
37
54
  class_variable_set :@@searchkick_options, options.dup
38
55
  class_variable_set :@@searchkick_klass, self
39
- class_variable_set :@@searchkick_index, index_name
40
- class_variable_set :@@searchkick_index_cache, {}
56
+ class_variable_set :@@searchkick_index_cache, Searchkick::IndexCache.new
41
57
 
42
58
  class << self
43
59
  def searchkick_search(term = "*", **options, &block)
44
- # TODO throw error in next major version
45
- Searchkick.warn("calling search on a relation is deprecated") if Searchkick.relation?(self)
60
+ if Searchkick.relation?(self)
61
+ raise Searchkick::Error, "search must be called on model, not relation"
62
+ end
46
63
 
47
64
  Searchkick.search(term, model: self, **options, &block)
48
65
  end
49
66
  alias_method Searchkick.search_method_name, :searchkick_search if Searchkick.search_method_name
50
67
 
51
68
  def searchkick_index(name: nil)
52
- index = name || class_variable_get(:@@searchkick_index)
53
- index = index.call if index.respond_to?(:call)
69
+ index_name = name || searchkick_klass.searchkick_index_name
70
+ index_name = index_name.call if index_name.respond_to?(:call)
54
71
  index_cache = class_variable_get(:@@searchkick_index_cache)
55
- index_cache[index] ||= Searchkick::Index.new(index, searchkick_options)
72
+ index_cache.fetch(index_name) { Searchkick::Index.new(index_name, searchkick_options) }
56
73
  end
57
74
  alias_method :search_index, :searchkick_index unless method_defined?(:search_index)
58
75
 
59
76
  def searchkick_reindex(method_name = nil, **options)
60
- # TODO relation = Searchkick.relation?(self)
61
- relation = (respond_to?(:current_scope) && respond_to?(:default_scoped) && current_scope && current_scope.to_sql != default_scoped.to_sql) ||
62
- (respond_to?(:queryable) && queryable != unscoped.with_default_scope)
63
-
64
- searchkick_index.reindex(searchkick_klass, method_name, scoped: relation, **options)
77
+ searchkick_index.reindex(self, method_name: method_name, **options)
65
78
  end
66
79
  alias_method :reindex, :searchkick_reindex unless method_defined?(:reindex)
67
80
 
68
81
  def searchkick_index_options
69
82
  searchkick_index.index_options
70
83
  end
84
+
85
+ def searchkick_index_name
86
+ @searchkick_index_name ||= begin
87
+ options = class_variable_get(:@@searchkick_options)
88
+ if options[:index_name]
89
+ options[:index_name]
90
+ elsif options[:index_prefix].respond_to?(:call)
91
+ -> { [options[:index_prefix].call, model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_") }
92
+ else
93
+ [options.key?(:index_prefix) ? options[:index_prefix] : Searchkick.index_prefix, model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_")
94
+ end
95
+ end
96
+ end
71
97
  end
72
98
 
73
99
  # always add callbacks, even when callbacks is false
@@ -78,33 +104,6 @@ module Searchkick
78
104
  after_save :reindex, if: -> { Searchkick.callbacks?(default: callbacks) }
79
105
  after_destroy :reindex, if: -> { Searchkick.callbacks?(default: callbacks) }
80
106
  end
81
-
82
- def reindex(method_name = nil, **options)
83
- RecordIndexer.new(self).reindex(method_name, **options)
84
- end unless method_defined?(:reindex)
85
-
86
- # TODO switch to keyword arguments
87
- def similar(options = {})
88
- self.class.searchkick_index.similar_record(self, **options)
89
- end unless method_defined?(:similar)
90
-
91
- def search_data
92
- data = respond_to?(:to_hash) ? to_hash : serializable_hash
93
- data.delete("id")
94
- data.delete("_id")
95
- data.delete("_type")
96
- data
97
- end unless method_defined?(:search_data)
98
-
99
- def should_index?
100
- true
101
- end unless method_defined?(:should_index?)
102
-
103
- if defined?(Cequel) && self < Cequel::Record && !method_defined?(:destroyed?)
104
- def destroyed?
105
- transient?
106
- end
107
- end
108
107
  end
109
108
  end
110
109
  end
@@ -3,34 +3,18 @@ module Searchkick
3
3
  queue_as { Searchkick.queue_name }
4
4
 
5
5
  def perform(class_name:, record_ids:, index_name: nil)
6
- # separate routing from id
7
- routing = Hash[record_ids.map { |r| r.split(/(?<!\|)\|(?!\|)/, 2).map { |v| v.gsub("||", "|") } }]
8
- record_ids = routing.keys
6
+ model = Searchkick.load_model(class_name)
7
+ index = model.searchkick_index(name: index_name)
9
8
 
10
- klass = class_name.constantize
11
- scope = Searchkick.load_records(klass, record_ids)
12
- scope = scope.search_import if scope.respond_to?(:search_import)
13
- records = scope.select(&:should_index?)
14
-
15
- # determine which records to delete
16
- delete_ids = record_ids - records.map { |r| r.id.to_s }
17
- delete_records = delete_ids.map do |id|
18
- m = klass.new
19
- m.id = id
20
- if routing[id]
21
- m.define_singleton_method(:search_routing) do
22
- routing[id]
23
- end
9
+ items =
10
+ record_ids.map do |r|
11
+ parts = r.split(/(?<!\|)\|(?!\|)/, 2)
12
+ .map { |v| v.gsub("||", "|") }
13
+ {id: parts[0], routing: parts[1]}
24
14
  end
25
- m
26
- end
27
15
 
28
- # bulk reindex
29
- index = klass.searchkick_index(name: index_name)
30
- Searchkick.callbacks(:bulk) do
31
- index.bulk_index(records) if records.any?
32
- index.bulk_delete(delete_records) if delete_records.any?
33
- end
16
+ relation = Searchkick.scope(model)
17
+ RecordIndexer.new(index).reindex_items(relation, items, method_name: nil)
34
18
  end
35
19
  end
36
20
  end
@@ -3,11 +3,12 @@ module Searchkick
3
3
  queue_as { Searchkick.queue_name }
4
4
 
5
5
  def perform(class_name:, index_name: nil, inline: false)
6
- model = class_name.constantize
6
+ model = Searchkick.load_model(class_name)
7
+ index = model.searchkick_index(name: index_name)
7
8
  limit = model.searchkick_options[:batch_size] || 1000
8
9
 
9
10
  loop do
10
- record_ids = model.searchkick_index(name: index_name).reindex_queue.reserve(limit: limit)
11
+ record_ids = index.reindex_queue.reserve(limit: limit)
11
12
  if record_ids.any?
12
13
  batch_options = {
13
14
  class_name: class_name,
@@ -18,7 +18,7 @@ module Searchkick
18
18
 
19
19
  def initialize(klass, term = "*", **options)
20
20
  unknown_keywords = options.keys - [:aggs, :block, :body, :body_options, :boost,
21
- :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :execute, :explain,
21
+ :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :explain,
22
22
  :fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load,
23
23
  :match, :misspellings, :models, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile,
24
24
  :request_params, :routing, :scope_results, :scroll, :select, :similar, :smart_aggs, :suggest, :total_entries, :track, :type, :where]
@@ -148,9 +148,6 @@ module Searchkick
148
148
  }
149
149
 
150
150
  if options[:debug]
151
- # can remove when minimum Ruby version is 2.5
152
- require "pp"
153
-
154
151
  puts "Searchkick Version: #{Searchkick::VERSION}"
155
152
  puts "Elasticsearch Version: #{Searchkick.server_version}"
156
153
  puts
@@ -190,11 +187,11 @@ module Searchkick
190
187
  end
191
188
 
192
189
  # set execute for multi search
193
- @execute = Searchkick::Results.new(searchkick_klass, response, opts)
190
+ @execute = Results.new(searchkick_klass, response, opts)
194
191
  end
195
192
 
196
193
  def retry_misspellings?(response)
197
- @misspellings_below && Searchkick::Results.new(searchkick_klass, response).total_count < @misspellings_below
194
+ @misspellings_below && Results.new(searchkick_klass, response).total_count < @misspellings_below
198
195
  end
199
196
 
200
197
  private
@@ -202,7 +199,11 @@ module Searchkick
202
199
  def handle_error(e)
203
200
  status_code = e.message[1..3].to_i
204
201
  if status_code == 404
205
- raise MissingIndexError, "Index missing - run #{reindex_command}"
202
+ if e.message.include?("No search context found for id")
203
+ raise MissingIndexError, "No search context found for id"
204
+ else
205
+ raise MissingIndexError, "Index missing - run #{reindex_command}"
206
+ end
206
207
  elsif status_code == 500 && (
207
208
  e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") ||
208
209
  e.message.include?("No query registered for [multi_match]") ||
@@ -210,14 +211,14 @@ module Searchkick
210
211
  e.message.include?("No query registered for [function_score]")
211
212
  )
212
213
 
213
- raise UnsupportedVersionError, "This version of Searchkick requires Elasticsearch 5 or greater"
214
+ raise UnsupportedVersionError
214
215
  elsif status_code == 400
215
216
  if (
216
217
  e.message.include?("bool query does not support [filter]") ||
217
218
  e.message.include?("[bool] filter does not support [filter]")
218
219
  )
219
220
 
220
- raise UnsupportedVersionError, "This version of Searchkick requires Elasticsearch 5 or greater"
221
+ raise UnsupportedVersionError
221
222
  elsif e.message =~ /analyzer \[searchkick_.+\] not found/
222
223
  raise InvalidQueryError, "Bad mapping - run #{reindex_command}"
223
224
  else
@@ -233,7 +234,14 @@ module Searchkick
233
234
  end
234
235
 
235
236
  def execute_search
236
- Searchkick.client.search(params)
237
+ name = searchkick_klass ? "#{searchkick_klass.name} Search" : "Search"
238
+ event = {
239
+ name: name,
240
+ query: params
241
+ }
242
+ ActiveSupport::Notifications.instrument("search.searchkick", event) do
243
+ Searchkick.client.search(params)
244
+ end
237
245
  end
238
246
 
239
247
  def prepare
@@ -247,9 +255,15 @@ module Searchkick
247
255
  default_limit = searchkick_options[:deep_paging] ? 1_000_000_000 : 10_000
248
256
  per_page = (options[:limit] || options[:per_page] || default_limit).to_i
249
257
  padding = [options[:padding].to_i, 0].max
250
- offset = options[:offset] || (page - 1) * per_page + padding
258
+ offset = (options[:offset] || (page - 1) * per_page + padding).to_i
251
259
  scroll = options[:scroll]
252
260
 
261
+ max_result_window = searchkick_options[:max_result_window]
262
+ if max_result_window
263
+ offset = max_result_window if offset > max_result_window
264
+ per_page = max_result_window - offset if offset + per_page > max_result_window
265
+ end
266
+
253
267
  # model and eager loading
254
268
  load = options[:load].nil? ? true : options[:load]
255
269
 
@@ -268,9 +282,10 @@ module Searchkick
268
282
  should = []
269
283
 
270
284
  if options[:similar]
285
+ like = options[:similar] == true ? term : options[:similar]
271
286
  query = {
272
287
  more_like_this: {
273
- like: term,
288
+ like: like,
274
289
  min_doc_freq: 1,
275
290
  min_term_freq: 1,
276
291
  analyzer: "searchkick_search2"
@@ -383,11 +398,6 @@ module Searchkick
383
398
 
384
399
  if field.start_with?("*.")
385
400
  q2 = qs.map { |q| {multi_match: q.merge(fields: [field], type: match_type == :match_phrase ? "phrase" : "best_fields")} }
386
- if below61?
387
- q2.each do |q|
388
- q[:multi_match].delete(:fuzzy_transpositions)
389
- end
390
- end
391
401
  else
392
402
  q2 = qs.map { |q| {match_type => {field => q}} }
393
403
  end
@@ -439,7 +449,7 @@ module Searchkick
439
449
  payload = {}
440
450
 
441
451
  # type when inheritance
442
- where = (options[:where] || {}).dup
452
+ where = ensure_permitted(options[:where] || {}).dup
443
453
  if searchkick_options[:inheritance] && (options[:type] || (klass != searchkick_klass && searchkick_index))
444
454
  where[:type] = [options[:type] || klass].flatten.map { |v| searchkick_index.klass_document_type(v, true) }
445
455
  end
@@ -499,7 +509,7 @@ module Searchkick
499
509
  set_highlights(payload, fields) if options[:highlight]
500
510
 
501
511
  # timeout shortly after client times out
502
- payload[:timeout] ||= "#{Searchkick.search_timeout + 1}s"
512
+ payload[:timeout] ||= "#{((Searchkick.search_timeout + 1) * 1000).round}ms"
503
513
 
504
514
  # An empty array will cause only the _id and _type for each hit to be returned
505
515
  # https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-source-filtering.html
@@ -696,9 +706,9 @@ module Searchkick
696
706
  def set_boost_by(multiply_filters, custom_filters)
697
707
  boost_by = options[:boost_by] || {}
698
708
  if boost_by.is_a?(Array)
699
- boost_by = Hash[boost_by.map { |f| [f, {factor: 1}] }]
709
+ boost_by = boost_by.to_h { |f| [f, {factor: 1}] }
700
710
  elsif boost_by.is_a?(Hash)
701
- multiply_by, boost_by = boost_by.partition { |_, v| v.delete(:boost_mode) == "multiply" }.map { |i| Hash[i] }
711
+ multiply_by, boost_by = boost_by.partition { |_, v| v.delete(:boost_mode) == "multiply" }.map(&:to_h)
702
712
  end
703
713
  boost_by[options[:boost]] = {factor: 1} if options[:boost]
704
714
 
@@ -763,7 +773,7 @@ module Searchkick
763
773
 
764
774
  def set_highlights(payload, fields)
765
775
  payload[:highlight] = {
766
- fields: Hash[fields.map { |f| [f, {}] }],
776
+ fields: fields.to_h { |f| [f, {}] },
767
777
  fragment_size: 0
768
778
  }
769
779
 
@@ -797,7 +807,7 @@ module Searchkick
797
807
  aggs = options[:aggs]
798
808
  payload[:aggs] = {}
799
809
 
800
- aggs = Hash[aggs.map { |f| [f, {}] }] if aggs.is_a?(Array) # convert to more advanced syntax
810
+ aggs = aggs.to_h { |f| [f, {}] } if aggs.is_a?(Array) # convert to more advanced syntax
801
811
  aggs.each do |field, agg_options|
802
812
  size = agg_options[:limit] ? agg_options[:limit] : 1_000
803
813
  shared_agg_options = agg_options.except(:limit, :field, :ranges, :date_ranges, :where)
@@ -836,8 +846,9 @@ module Searchkick
836
846
  end
837
847
 
838
848
  where = {}
839
- where = (options[:where] || {}).reject { |k| k == field } unless options[:smart_aggs] == false
840
- agg_filters = where_filters(where.merge(agg_options[:where] || {}))
849
+ where = ensure_permitted(options[:where] || {}).reject { |k| k == field } unless options[:smart_aggs] == false
850
+ agg_where = ensure_permitted(agg_options[:where] || {})
851
+ agg_filters = where_filters(where.merge(agg_where))
841
852
 
842
853
  # only do one level comparison for simplicity
843
854
  filters.select! do |filter|
@@ -873,19 +884,16 @@ module Searchkick
873
884
  end
874
885
 
875
886
  def set_order(payload)
876
- order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
877
- id_field = :_id
878
- # TODO no longer map id to _id in Searchkick 5
879
- # since sorting on _id is deprecated in Elasticsearch
880
- payload[:sort] = order.is_a?(Array) ? order : Hash[order.map { |k, v| [k.to_s == "id" ? id_field : k, v] }]
887
+ payload[:sort] = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
881
888
  end
882
889
 
883
- def where_filters(where)
884
- # if where.respond_to?(:permitted?) && !where.permitted?
885
- # # TODO check in more places
886
- # Searchkick.warn("Passing unpermitted parameters will raise an exception in Searchkick 5")
887
- # end
890
+ # provides *very* basic protection from unfiltered parameters
891
+ # this is not meant to be comprehensive and may be expanded in the future
892
+ def ensure_permitted(obj)
893
+ obj.to_h
894
+ end
888
895
 
896
+ def where_filters(where)
889
897
  filters = []
890
898
  (where || {}).each do |field, value|
891
899
  field = :_id if field.to_s == "id"
@@ -1007,7 +1015,7 @@ module Searchkick
1007
1015
  when :lte
1008
1016
  {to: op_value, include_upper: true}
1009
1017
  else
1010
- raise "Unknown where operator: #{op.inspect}"
1018
+ raise ArgumentError, "Unknown where operator: #{op.inspect}"
1011
1019
  end
1012
1020
  # issue 132
1013
1021
  if (existing = filters.find { |f| f[:range] && f[:range][field] })
@@ -1036,29 +1044,25 @@ module Searchkick
1036
1044
  {bool: {must_not: {exists: {field: field}}}}
1037
1045
  elsif value.is_a?(Regexp)
1038
1046
  source = value.source
1039
- unless source.start_with?("\\A") && source.end_with?("\\z")
1040
- # https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html
1041
- Searchkick.warn("Regular expressions are always anchored in Elasticsearch")
1042
- end
1047
+
1048
+ # TODO handle other regexp options
1043
1049
 
1044
1050
  # TODO handle other anchor characters, like ^, $, \Z
1045
1051
  if source.start_with?("\\A")
1046
1052
  source = source[2..-1]
1047
1053
  else
1048
- # TODO uncomment in Searchkick 5
1049
- # source = ".*#{source}"
1054
+ source = ".*#{source}"
1050
1055
  end
1051
1056
 
1052
1057
  if source.end_with?("\\z")
1053
1058
  source = source[0..-3]
1054
1059
  else
1055
- # TODO uncomment in Searchkick 5
1056
- # source = "#{source}.*"
1060
+ source = "#{source}.*"
1057
1061
  end
1058
1062
 
1059
1063
  if below710?
1060
1064
  if value.casefold?
1061
- Searchkick.warn("Case-insensitive flag does not work with Elasticsearch < 7.10")
1065
+ raise ArgumentError, "Case-insensitive flag does not work with Elasticsearch < 7.10"
1062
1066
  end
1063
1067
  {regexp: {field => {value: source, flags: "NONE"}}}
1064
1068
  else
@@ -1069,9 +1073,7 @@ module Searchkick
1069
1073
  if value.as_json.is_a?(Enumerable)
1070
1074
  # query will fail, but this is better
1071
1075
  # same message as Active Record
1072
- # TODO make TypeError
1073
- # raise InvalidQueryError for backward compatibility
1074
- raise Searchkick::InvalidQueryError, "can't cast #{value.class.name}"
1076
+ raise TypeError, "can't cast #{value.class.name}"
1075
1077
  end
1076
1078
 
1077
1079
  {term: {field => {value: value}}}
@@ -1150,21 +1152,13 @@ module Searchkick
1150
1152
  end
1151
1153
 
1152
1154
  def track_total_hits?
1153
- (searchkick_options[:deep_paging] && !below70?) || body_options[:track_total_hits]
1155
+ searchkick_options[:deep_paging] || body_options[:track_total_hits]
1154
1156
  end
1155
1157
 
1156
1158
  def body_options
1157
1159
  options[:body_options] || {}
1158
1160
  end
1159
1161
 
1160
- def below61?
1161
- Searchkick.server_below?("6.1.0")
1162
- end
1163
-
1164
- def below70?
1165
- Searchkick.server_below?("7.0.0")
1166
- end
1167
-
1168
1162
  def below73?
1169
1163
  Searchkick.server_below?("7.3.0")
1170
1164
  end
@@ -25,6 +25,7 @@ module Searchkick
25
25
  {delete: record_data}
26
26
  end
27
27
 
28
+ # custom id can be useful for load: false
28
29
  def search_id
29
30
  id = record.respond_to?(:search_document_id) ? record.search_document_id : record.id
30
31
  id.is_a?(Numeric) ? id : id.to_s
@@ -39,7 +40,6 @@ module Searchkick
39
40
  _index: index.name,
40
41
  _id: search_id
41
42
  }
42
- data[:_type] = document_type if Searchkick.server_below7?
43
43
  data[:routing] = record.search_routing if record.respond_to?(:search_routing)
44
44
  data
45
45
  end