searchkick 4.6.3 → 5.5.2
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 +142 -2
- data/LICENSE.txt +1 -1
- data/README.md +424 -228
- data/lib/searchkick/bulk_reindex_job.rb +12 -8
- data/lib/searchkick/controller_runtime.rb +40 -0
- data/lib/searchkick/index.rb +152 -67
- data/lib/searchkick/index_cache.rb +30 -0
- data/lib/searchkick/index_options.rb +102 -68
- data/lib/searchkick/indexer.rb +15 -8
- data/lib/searchkick/log_subscriber.rb +57 -0
- data/lib/searchkick/middleware.rb +9 -2
- data/lib/searchkick/model.rb +50 -51
- data/lib/searchkick/process_batch_job.rb +9 -25
- data/lib/searchkick/process_queue_job.rb +3 -2
- data/lib/searchkick/query.rb +212 -69
- data/lib/searchkick/railtie.rb +1 -1
- data/lib/searchkick/record_data.rb +1 -1
- data/lib/searchkick/record_indexer.rb +136 -52
- data/lib/searchkick/reindex_queue.rb +36 -8
- data/lib/searchkick/reindex_v2_job.rb +10 -34
- data/lib/searchkick/relation.rb +247 -0
- data/lib/searchkick/relation_indexer.rb +155 -0
- data/lib/searchkick/reranking.rb +28 -0
- data/lib/searchkick/results.rb +29 -28
- data/lib/searchkick/script.rb +11 -0
- data/lib/searchkick/version.rb +1 -1
- data/lib/searchkick/where.rb +11 -0
- data/lib/searchkick.rb +197 -95
- data/lib/tasks/searchkick.rake +6 -3
- metadata +14 -32
- data/lib/searchkick/bulk_indexer.rb +0 -173
- data/lib/searchkick/logging.rb +0 -246
data/lib/searchkick/query.rb
CHANGED
@@ -9,7 +9,7 @@ module Searchkick
|
|
9
9
|
attr_accessor :body
|
10
10
|
|
11
11
|
def_delegators :execute, :map, :each, :any?, :empty?, :size, :length, :slice, :[], :to_ary,
|
12
|
-
:
|
12
|
+
:results, :suggestions, :each_with_hit, :with_details, :aggregations, :aggs,
|
13
13
|
:took, :error, :model_name, :entry_name, :total_count, :total_entries,
|
14
14
|
:current_page, :per_page, :limit_value, :padding, :total_pages, :num_pages,
|
15
15
|
:offset_value, :offset, :previous_page, :prev_page, :next_page, :first_page?, :last_page?,
|
@@ -18,8 +18,8 @@ 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, :
|
22
|
-
:fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load,
|
21
|
+
:boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :explain,
|
22
|
+
:fields, :highlight, :includes, :index_name, :indices_boost, :knn, :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]
|
25
25
|
raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
|
@@ -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 =
|
190
|
+
@execute = Results.new(searchkick_klass, response, opts)
|
194
191
|
end
|
195
192
|
|
196
193
|
def retry_misspellings?(response)
|
197
|
-
@misspellings_below &&
|
194
|
+
@misspellings_below && response["error"].nil? && 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
|
-
|
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,15 +211,15 @@ module Searchkick
|
|
210
211
|
e.message.include?("No query registered for [function_score]")
|
211
212
|
)
|
212
213
|
|
213
|
-
raise UnsupportedVersionError
|
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
|
221
|
-
elsif e.message
|
221
|
+
raise UnsupportedVersionError
|
222
|
+
elsif e.message.match?(/analyzer \[searchkick_.+\] not found/)
|
222
223
|
raise InvalidQueryError, "Bad mapping - run #{reindex_command}"
|
223
224
|
else
|
224
225
|
raise InvalidQueryError, e.message
|
@@ -233,7 +234,14 @@ module Searchkick
|
|
233
234
|
end
|
234
235
|
|
235
236
|
def execute_search
|
236
|
-
|
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,16 @@ 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
|
+
original_per_page = per_page
|
263
|
+
if max_result_window
|
264
|
+
offset = max_result_window if offset > max_result_window
|
265
|
+
per_page = max_result_window - offset if offset + per_page > max_result_window
|
266
|
+
end
|
267
|
+
|
253
268
|
# model and eager loading
|
254
269
|
load = options[:load].nil? ? true : options[:load]
|
255
270
|
|
@@ -268,9 +283,10 @@ module Searchkick
|
|
268
283
|
should = []
|
269
284
|
|
270
285
|
if options[:similar]
|
286
|
+
like = options[:similar] == true ? term : options[:similar]
|
271
287
|
query = {
|
272
288
|
more_like_this: {
|
273
|
-
like:
|
289
|
+
like: like,
|
274
290
|
min_doc_freq: 1,
|
275
291
|
min_term_freq: 1,
|
276
292
|
analyzer: "searchkick_search2"
|
@@ -358,7 +374,7 @@ module Searchkick
|
|
358
374
|
field_misspellings = misspellings && (!misspellings_fields || misspellings_fields.include?(base_field(field)))
|
359
375
|
|
360
376
|
if field == "_all" || field.end_with?(".analyzed")
|
361
|
-
shared_options[:cutoff_frequency] = 0.001 unless operator.to_s == "and" || field_misspellings == false || (!below73? && !track_total_hits?)
|
377
|
+
shared_options[:cutoff_frequency] = 0.001 unless operator.to_s == "and" || field_misspellings == false || (!below73? && !track_total_hits?) || match_type == :match_phrase || !below80? || Searchkick.opensearch?
|
362
378
|
qs << shared_options.merge(analyzer: "searchkick_search")
|
363
379
|
|
364
380
|
# searchkick_search and searchkick_search2 are the same for some languages
|
@@ -372,7 +388,7 @@ module Searchkick
|
|
372
388
|
exclude_field = f
|
373
389
|
exclude_analyzer = "keyword"
|
374
390
|
else
|
375
|
-
analyzer = field
|
391
|
+
analyzer = field.match?(/\.word_(start|middle|end)\z/) ? "searchkick_word_search" : "searchkick_autocomplete_search"
|
376
392
|
qs << shared_options.merge(analyzer: analyzer)
|
377
393
|
exclude_analyzer = analyzer
|
378
394
|
end
|
@@ -383,11 +399,6 @@ module Searchkick
|
|
383
399
|
|
384
400
|
if field.start_with?("*.")
|
385
401
|
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
402
|
else
|
392
403
|
q2 = qs.map { |q| {match_type => {field => q}} }
|
393
404
|
end
|
@@ -439,7 +450,7 @@ module Searchkick
|
|
439
450
|
payload = {}
|
440
451
|
|
441
452
|
# type when inheritance
|
442
|
-
where = (options[:where] || {}).dup
|
453
|
+
where = ensure_permitted(options[:where] || {}).dup
|
443
454
|
if searchkick_options[:inheritance] && (options[:type] || (klass != searchkick_klass && searchkick_index))
|
444
455
|
where[:type] = [options[:type] || klass].flatten.map { |v| searchkick_index.klass_document_type(v, true) }
|
445
456
|
end
|
@@ -499,7 +510,7 @@ module Searchkick
|
|
499
510
|
set_highlights(payload, fields) if options[:highlight]
|
500
511
|
|
501
512
|
# timeout shortly after client times out
|
502
|
-
payload[:timeout] ||= "#{Searchkick.search_timeout + 1}
|
513
|
+
payload[:timeout] ||= "#{((Searchkick.search_timeout + 1) * 1000).round}ms"
|
503
514
|
|
504
515
|
# An empty array will cause only the _id and _type for each hit to be returned
|
505
516
|
# https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-source-filtering.html
|
@@ -515,6 +526,9 @@ module Searchkick
|
|
515
526
|
end
|
516
527
|
end
|
517
528
|
|
529
|
+
# knn
|
530
|
+
set_knn(payload, options[:knn], per_page, offset) if options[:knn]
|
531
|
+
|
518
532
|
# pagination
|
519
533
|
pagination_options = options[:page] || options[:limit] || options[:per_page] || options[:offset] || options[:padding]
|
520
534
|
if !options[:body] || pagination_options
|
@@ -548,7 +562,7 @@ module Searchkick
|
|
548
562
|
|
549
563
|
@body = payload
|
550
564
|
@page = page
|
551
|
-
@per_page =
|
565
|
+
@per_page = original_per_page
|
552
566
|
@padding = padding
|
553
567
|
@load = load
|
554
568
|
@scroll = scroll
|
@@ -696,9 +710,9 @@ module Searchkick
|
|
696
710
|
def set_boost_by(multiply_filters, custom_filters)
|
697
711
|
boost_by = options[:boost_by] || {}
|
698
712
|
if boost_by.is_a?(Array)
|
699
|
-
boost_by =
|
713
|
+
boost_by = boost_by.to_h { |f| [f, {factor: 1}] }
|
700
714
|
elsif boost_by.is_a?(Hash)
|
701
|
-
multiply_by, boost_by = boost_by.partition { |_, v| v.delete(:boost_mode) == "multiply" }.map
|
715
|
+
multiply_by, boost_by = boost_by.partition { |_, v| v.delete(:boost_mode) == "multiply" }.map(&:to_h)
|
702
716
|
end
|
703
717
|
boost_by[options[:boost]] = {factor: 1} if options[:boost]
|
704
718
|
|
@@ -763,7 +777,7 @@ module Searchkick
|
|
763
777
|
|
764
778
|
def set_highlights(payload, fields)
|
765
779
|
payload[:highlight] = {
|
766
|
-
fields:
|
780
|
+
fields: fields.to_h { |f| [f, {}] },
|
767
781
|
fragment_size: 0
|
768
782
|
}
|
769
783
|
|
@@ -797,7 +811,7 @@ module Searchkick
|
|
797
811
|
aggs = options[:aggs]
|
798
812
|
payload[:aggs] = {}
|
799
813
|
|
800
|
-
aggs =
|
814
|
+
aggs = aggs.to_h { |f| [f, {}] } if aggs.is_a?(Array) # convert to more advanced syntax
|
801
815
|
aggs.each do |field, agg_options|
|
802
816
|
size = agg_options[:limit] ? agg_options[:limit] : 1_000
|
803
817
|
shared_agg_options = agg_options.except(:limit, :field, :ranges, :date_ranges, :where)
|
@@ -836,8 +850,9 @@ module Searchkick
|
|
836
850
|
end
|
837
851
|
|
838
852
|
where = {}
|
839
|
-
where = (options[:where] || {}).reject { |k| k == field } unless options[:smart_aggs] == false
|
840
|
-
|
853
|
+
where = ensure_permitted(options[:where] || {}).reject { |k| k == field } unless options[:smart_aggs] == false
|
854
|
+
agg_where = ensure_permitted(agg_options[:where] || {})
|
855
|
+
agg_filters = where_filters(where.merge(agg_where))
|
841
856
|
|
842
857
|
# only do one level comparison for simplicity
|
843
858
|
filters.select! do |filter|
|
@@ -864,6 +879,129 @@ module Searchkick
|
|
864
879
|
end
|
865
880
|
end
|
866
881
|
|
882
|
+
def set_knn(payload, knn, per_page, offset)
|
883
|
+
if term != "*"
|
884
|
+
raise ArgumentError, "Use Searchkick.multi_search for hybrid search"
|
885
|
+
end
|
886
|
+
|
887
|
+
field = knn[:field]
|
888
|
+
field_options = searchkick_options.dig(:knn, field.to_sym) || searchkick_options.dig(:knn, field.to_s) || {}
|
889
|
+
vector = knn[:vector]
|
890
|
+
distance = knn[:distance] || field_options[:distance]
|
891
|
+
exact = knn[:exact]
|
892
|
+
exact = field_options[:distance].nil? || distance != field_options[:distance] if exact.nil?
|
893
|
+
k = per_page + offset
|
894
|
+
ef_search = knn[:ef_search]
|
895
|
+
filter = payload.delete(:query)
|
896
|
+
|
897
|
+
if distance.nil?
|
898
|
+
raise ArgumentError, "distance required"
|
899
|
+
elsif !exact && distance != field_options[:distance]
|
900
|
+
raise ArgumentError, "distance must match searchkick options for approximate search"
|
901
|
+
end
|
902
|
+
|
903
|
+
if Searchkick.opensearch?
|
904
|
+
if exact
|
905
|
+
# https://opensearch.org/docs/latest/search-plugins/knn/knn-score-script/#spaces
|
906
|
+
space_type =
|
907
|
+
case distance
|
908
|
+
when "cosine"
|
909
|
+
"cosinesimil"
|
910
|
+
when "euclidean"
|
911
|
+
"l2"
|
912
|
+
when "taxicab"
|
913
|
+
"l1"
|
914
|
+
when "inner_product"
|
915
|
+
"innerproduct"
|
916
|
+
when "chebyshev"
|
917
|
+
"linf"
|
918
|
+
else
|
919
|
+
raise ArgumentError, "Unknown distance: #{distance}"
|
920
|
+
end
|
921
|
+
|
922
|
+
payload[:query] = {
|
923
|
+
script_score: {
|
924
|
+
query: {
|
925
|
+
bool: {
|
926
|
+
must: [filter, {exists: {field: field}}]
|
927
|
+
}
|
928
|
+
},
|
929
|
+
script: {
|
930
|
+
source: "knn_score",
|
931
|
+
lang: "knn",
|
932
|
+
params: {
|
933
|
+
field: field,
|
934
|
+
query_value: vector,
|
935
|
+
space_type: space_type
|
936
|
+
}
|
937
|
+
},
|
938
|
+
boost: distance == "cosine" && Searchkick.server_below?("2.19.0", true) ? 0.5 : 1.0
|
939
|
+
}
|
940
|
+
}
|
941
|
+
else
|
942
|
+
if ef_search && Searchkick.server_below?("2.16.0", true)
|
943
|
+
raise Error, "ef_search requires OpenSearch 2.16+"
|
944
|
+
end
|
945
|
+
|
946
|
+
payload[:query] = {
|
947
|
+
knn: {
|
948
|
+
field.to_sym => {
|
949
|
+
vector: vector,
|
950
|
+
k: k,
|
951
|
+
filter: filter
|
952
|
+
}.merge(ef_search ? {method_parameters: {ef_search: ef_search}} : {})
|
953
|
+
}
|
954
|
+
}
|
955
|
+
end
|
956
|
+
else
|
957
|
+
if exact
|
958
|
+
# prevent incorrect distances/results with Elasticsearch 9.0.0-rc1
|
959
|
+
if !below90? && field_options[:distance] == "cosine" && distance != "cosine"
|
960
|
+
raise ArgumentError, "distance must match searchkick options"
|
961
|
+
end
|
962
|
+
|
963
|
+
# https://github.com/elastic/elasticsearch/blob/main/docs/reference/vectors/vector-functions.asciidoc
|
964
|
+
source =
|
965
|
+
case distance
|
966
|
+
when "cosine"
|
967
|
+
"(cosineSimilarity(params.query_vector, params.field) + 1.0) * 0.5"
|
968
|
+
when "euclidean"
|
969
|
+
"double l2 = l2norm(params.query_vector, params.field); 1 / (1 + l2 * l2)"
|
970
|
+
when "taxicab"
|
971
|
+
"1 / (1 + l1norm(params.query_vector, params.field))"
|
972
|
+
when "inner_product"
|
973
|
+
"double dot = dotProduct(params.query_vector, params.field); dot > 0 ? dot + 1 : 1 / (1 - dot)"
|
974
|
+
else
|
975
|
+
raise ArgumentError, "Unknown distance: #{distance}"
|
976
|
+
end
|
977
|
+
|
978
|
+
payload[:query] = {
|
979
|
+
script_score: {
|
980
|
+
query: {
|
981
|
+
bool: {
|
982
|
+
must: [filter, {exists: {field: field}}]
|
983
|
+
}
|
984
|
+
},
|
985
|
+
script: {
|
986
|
+
source: source,
|
987
|
+
params: {
|
988
|
+
field: field,
|
989
|
+
query_vector: vector
|
990
|
+
}
|
991
|
+
}
|
992
|
+
}
|
993
|
+
}
|
994
|
+
else
|
995
|
+
payload[:knn] = {
|
996
|
+
field: field,
|
997
|
+
query_vector: vector,
|
998
|
+
k: k,
|
999
|
+
filter: filter
|
1000
|
+
}.merge(ef_search ? {num_candidates: ef_search} : {})
|
1001
|
+
end
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
867
1005
|
def set_post_filters(payload, post_filters)
|
868
1006
|
payload[:post_filter] = {
|
869
1007
|
bool: {
|
@@ -873,19 +1011,17 @@ module Searchkick
|
|
873
1011
|
end
|
874
1012
|
|
875
1013
|
def set_order(payload)
|
876
|
-
|
877
|
-
|
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] }]
|
1014
|
+
value = options[:order]
|
1015
|
+
payload[:sort] = value.is_a?(Enumerable) ? value : {value => :asc}
|
881
1016
|
end
|
882
1017
|
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
1018
|
+
# provides *very* basic protection from unfiltered parameters
|
1019
|
+
# this is not meant to be comprehensive and may be expanded in the future
|
1020
|
+
def ensure_permitted(obj)
|
1021
|
+
obj.to_h
|
1022
|
+
end
|
888
1023
|
|
1024
|
+
def where_filters(where)
|
889
1025
|
filters = []
|
890
1026
|
(where || {}).each do |field, value|
|
891
1027
|
field = :_id if field.to_s == "id"
|
@@ -900,8 +1036,12 @@ module Searchkick
|
|
900
1036
|
filters << {bool: {must_not: where_filters(value)}}
|
901
1037
|
elsif field == :_and
|
902
1038
|
filters << {bool: {must: value.map { |or_statement| {bool: {filter: where_filters(or_statement)}} }}}
|
903
|
-
|
904
|
-
|
1039
|
+
elsif field == :_script
|
1040
|
+
unless value.is_a?(Script)
|
1041
|
+
raise TypeError, "expected Searchkick::Script"
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
filters << {script: {script: {source: value.source, lang: value.lang, params: value.params}}}
|
905
1045
|
else
|
906
1046
|
# expand ranges
|
907
1047
|
if value.is_a?(Range)
|
@@ -994,20 +1134,29 @@ module Searchkick
|
|
994
1134
|
when :in
|
995
1135
|
filters << term_filters(field, op_value)
|
996
1136
|
when :exists
|
1137
|
+
# TODO add support for false in Searchkick 6
|
1138
|
+
if op_value != true
|
1139
|
+
# TODO raise error in Searchkick 6
|
1140
|
+
Searchkick.warn("Passing a value other than true to exists is not supported")
|
1141
|
+
end
|
997
1142
|
filters << {exists: {field: field}}
|
998
1143
|
else
|
999
1144
|
range_query =
|
1000
1145
|
case op
|
1001
1146
|
when :gt
|
1002
|
-
|
1147
|
+
# TODO always use gt in Searchkick 6
|
1148
|
+
below90? ? {from: op_value, include_lower: false} : {gt: op_value}
|
1003
1149
|
when :gte
|
1004
|
-
|
1150
|
+
# TODO always use gte in Searchkick 6
|
1151
|
+
below90? ? {from: op_value, include_lower: true} : {gte: op_value}
|
1005
1152
|
when :lt
|
1006
|
-
|
1153
|
+
# TODO always use lt in Searchkick 6
|
1154
|
+
below90? ? {to: op_value, include_upper: false} : {lt: op_value}
|
1007
1155
|
when :lte
|
1008
|
-
|
1156
|
+
# TODO always use lte in Searchkick 6
|
1157
|
+
below90? ? {to: op_value, include_upper: true} : {lte: op_value}
|
1009
1158
|
else
|
1010
|
-
raise "Unknown where operator: #{op.inspect}"
|
1159
|
+
raise ArgumentError, "Unknown where operator: #{op.inspect}"
|
1011
1160
|
end
|
1012
1161
|
# issue 132
|
1013
1162
|
if (existing = filters.find { |f| f[:range] && f[:range][field] })
|
@@ -1036,29 +1185,25 @@ module Searchkick
|
|
1036
1185
|
{bool: {must_not: {exists: {field: field}}}}
|
1037
1186
|
elsif value.is_a?(Regexp)
|
1038
1187
|
source = value.source
|
1039
|
-
|
1040
|
-
|
1041
|
-
Searchkick.warn("Regular expressions are always anchored in Elasticsearch")
|
1042
|
-
end
|
1188
|
+
|
1189
|
+
# TODO handle other regexp options
|
1043
1190
|
|
1044
1191
|
# TODO handle other anchor characters, like ^, $, \Z
|
1045
1192
|
if source.start_with?("\\A")
|
1046
1193
|
source = source[2..-1]
|
1047
1194
|
else
|
1048
|
-
|
1049
|
-
# source = ".*#{source}"
|
1195
|
+
source = ".*#{source}"
|
1050
1196
|
end
|
1051
1197
|
|
1052
1198
|
if source.end_with?("\\z")
|
1053
1199
|
source = source[0..-3]
|
1054
1200
|
else
|
1055
|
-
|
1056
|
-
# source = "#{source}.*"
|
1201
|
+
source = "#{source}.*"
|
1057
1202
|
end
|
1058
1203
|
|
1059
1204
|
if below710?
|
1060
1205
|
if value.casefold?
|
1061
|
-
|
1206
|
+
raise ArgumentError, "Case-insensitive flag does not work with Elasticsearch < 7.10"
|
1062
1207
|
end
|
1063
1208
|
{regexp: {field => {value: source, flags: "NONE"}}}
|
1064
1209
|
else
|
@@ -1069,9 +1214,7 @@ module Searchkick
|
|
1069
1214
|
if value.as_json.is_a?(Enumerable)
|
1070
1215
|
# query will fail, but this is better
|
1071
1216
|
# same message as Active Record
|
1072
|
-
|
1073
|
-
# raise InvalidQueryError for backward compatibility
|
1074
|
-
raise Searchkick::InvalidQueryError, "can't cast #{value.class.name}"
|
1217
|
+
raise TypeError, "can't cast #{value.class.name}"
|
1075
1218
|
end
|
1076
1219
|
|
1077
1220
|
{term: {field => {value: value}}}
|
@@ -1150,21 +1293,13 @@ module Searchkick
|
|
1150
1293
|
end
|
1151
1294
|
|
1152
1295
|
def track_total_hits?
|
1153
|
-
|
1296
|
+
searchkick_options[:deep_paging] || body_options[:track_total_hits]
|
1154
1297
|
end
|
1155
1298
|
|
1156
1299
|
def body_options
|
1157
1300
|
options[:body_options] || {}
|
1158
1301
|
end
|
1159
1302
|
|
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
1303
|
def below73?
|
1169
1304
|
Searchkick.server_below?("7.3.0")
|
1170
1305
|
end
|
@@ -1176,5 +1311,13 @@ module Searchkick
|
|
1176
1311
|
def below710?
|
1177
1312
|
Searchkick.server_below?("7.10.0")
|
1178
1313
|
end
|
1314
|
+
|
1315
|
+
def below80?
|
1316
|
+
Searchkick.server_below?("8.0.0")
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
def below90?
|
1320
|
+
Searchkick.server_below?("9.0.0")
|
1321
|
+
end
|
1179
1322
|
end
|
1180
1323
|
end
|
data/lib/searchkick/railtie.rb
CHANGED
@@ -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
|