elastomer-client 2.3.0 → 3.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +0 -4
  4. data/CHANGELOG.md +8 -0
  5. data/docker/docker-compose.cibuild.yml +8 -0
  6. data/docker/docker-compose.es24.yml +34 -0
  7. data/docker/docker-compose.es56.yml +37 -0
  8. data/docker/elasticsearch.yml +15 -0
  9. data/docs/index.md +2 -2
  10. data/docs/notifications.md +1 -1
  11. data/elastomer-client.gemspec +2 -0
  12. data/lib/elastomer/client.rb +86 -33
  13. data/lib/elastomer/client/app_delete_by_query.rb +158 -0
  14. data/lib/elastomer/client/delete_by_query.rb +8 -115
  15. data/lib/elastomer/client/docs.rb +63 -13
  16. data/lib/elastomer/client/errors.rb +10 -2
  17. data/lib/elastomer/client/index.rb +40 -12
  18. data/lib/elastomer/client/multi_percolate.rb +2 -2
  19. data/lib/elastomer/client/native_delete_by_query.rb +60 -0
  20. data/lib/elastomer/client/percolator.rb +6 -3
  21. data/lib/elastomer/client/scroller.rb +22 -7
  22. data/lib/elastomer/client/tasks.rb +188 -0
  23. data/lib/elastomer/client/warmer.rb +6 -0
  24. data/lib/elastomer/notifications.rb +1 -0
  25. data/lib/elastomer/version.rb +1 -1
  26. data/lib/elastomer/version_support.rb +177 -0
  27. data/script/cibuild +77 -6
  28. data/script/cibuild-elastomer-client +1 -0
  29. data/script/cibuild-elastomer-client-es24 +8 -0
  30. data/script/cibuild-elastomer-client-es56 +8 -0
  31. data/script/poll-for-es +20 -0
  32. data/test/client/{delete_by_query_test.rb → app_delete_by_query_test.rb} +7 -7
  33. data/test/client/bulk_test.rb +9 -13
  34. data/test/client/cluster_test.rb +2 -2
  35. data/test/client/docs_test.rb +133 -49
  36. data/test/client/errors_test.rb +21 -1
  37. data/test/client/es_5_x_warmer_test.rb +13 -0
  38. data/test/client/index_test.rb +104 -39
  39. data/test/client/multi_percolate_test.rb +13 -6
  40. data/test/client/multi_search_test.rb +5 -5
  41. data/test/client/native_delete_by_query_test.rb +123 -0
  42. data/test/client/nodes_test.rb +1 -1
  43. data/test/client/percolator_test.rb +10 -2
  44. data/test/client/repository_test.rb +1 -1
  45. data/test/client/scroller_test.rb +16 -6
  46. data/test/client/snapshot_test.rb +1 -1
  47. data/test/client/stubbed_client_test.rb +1 -1
  48. data/test/client/tasks_test.rb +139 -0
  49. data/test/client/template_test.rb +1 -1
  50. data/test/client/warmer_test.rb +8 -4
  51. data/test/client_test.rb +99 -0
  52. data/test/core_ext/time_test.rb +1 -1
  53. data/test/notifications_test.rb +4 -0
  54. data/test/test_helper.rb +129 -21
  55. data/test/version_support_test.rb +119 -0
  56. metadata +59 -5
@@ -1,120 +1,13 @@
1
1
  module Elastomer
2
2
  class Client
3
-
4
- # Delete documents from one or more indices and one or more types based
5
- # on a query.
3
+ # Execute delete_by_query using the native _delete_by_query API if supported
4
+ # or the application-level implementation.
6
5
  #
7
- # The return value follows the format returned by the Elasticsearch Delete
8
- # by Query plugin: https://github.com/elastic/elasticsearch/blob/master/docs/plugins/delete-by-query.asciidoc#response-body
9
- #
10
- # Internally, this method uses a combination of scroll and bulk delete
11
- # instead of the Delete by Query API, which was removed in Elasticsearch
12
- # 2.0.
13
- #
14
- # query - The query body as a Hash
15
- # params - Parameters Hash
16
- #
17
- # Examples
18
- #
19
- # # request body query
20
- # delete_by_query({:query => {:match_all => {}}}, :type => 'tweet')
21
- #
22
- # # same thing but using the URI request method
23
- # delete_by_query(nil, { :q => '*:*', :type => 'tweet' })
24
- #
25
- # See https://www.elastic.co/guide/en/elasticsearch/plugins/current/delete-by-query-usage.html
26
- #
27
- # Returns a Hash of statistics about the delete operations, for example:
28
- #
29
- # {
30
- # "took" : 639,
31
- # "_indices" : {
32
- # "_all" : {
33
- # "found" : 5901,
34
- # "deleted" : 5901,
35
- # "missing" : 0,
36
- # "failed" : 0
37
- # },
38
- # "twitter" : {
39
- # "found" : 5901,
40
- # "deleted" : 5901,
41
- # "missing" : 0,
42
- # "failed" : 0
43
- # }
44
- # },
45
- # "failures" : [ ]
46
- # }
6
+ # Warning: These implementations have different parameters and return types.
7
+ # If you want to use one or the other consistently, use Elastomer::Client#native_delete_by_query
8
+ # or Elastomer::Client#app_delete_by_query directly.
47
9
  def delete_by_query(query, params = {})
48
- DeleteByQuery.new(self, query, params).execute
10
+ send(version_support.delete_by_query_method, query, params)
49
11
  end
50
-
51
- class DeleteByQuery
52
-
53
- # Create a new DeleteByQuery command for deleting documents matching a
54
- # query
55
- #
56
- # client - Elastomer::Client used for HTTP requests to the server
57
- # query - The query used to find documents to delete
58
- # params - Other URL parameters
59
- def initialize(client, query, params = {})
60
- @client = client
61
- @query = query
62
- @params = params
63
- @response_stats = { "took" => 0, "_indices" => { "_all" => {} }, "failures" => [] }
64
- end
65
-
66
- attr_reader :client, :query, :params, :response_stats
67
-
68
- # Internal: Determine whether or not an HTTP status code is in the range
69
- # 200 to 299
70
- #
71
- # status - HTTP status code
72
- #
73
- # Returns a boolean
74
- def is_ok?(status)
75
- status.between?(200, 299)
76
- end
77
-
78
- # Internal: Tally the contributions of an item to the found, deleted,
79
- # missing, and failed counts for the summary statistics
80
- #
81
- # item - An element of the items array from a bulk response
82
- #
83
- # Returns a Hash of counts for each category
84
- def categorize(item)
85
- {
86
- "found" => item["found"] || item["status"] == 409 ? 1 : 0,
87
- "deleted" => is_ok?(item["status"]) ? 1 : 0,
88
- "missing" => !item["found"] && !item.key?("error") ? 1 : 0,
89
- "failed" => item.key?("error") ? 1 : 0,
90
- }
91
- end
92
-
93
- # Internal: Combine a response item with the existing statistics
94
- #
95
- # item - A bulk response item
96
- def accumulate(item)
97
- item = item["delete"]
98
- (@response_stats["_indices"][item["_index"]] ||= {}).merge!(categorize(item)) { |_, n, m| n + m }
99
- @response_stats["_indices"]["_all"].merge!(categorize(item)) { |_, n, m| n + m }
100
- @response_stats["failures"] << item unless is_ok? item["status"]
101
- end
102
-
103
- # Perform the Delete by Query action
104
- #
105
- # Returns a Hash of statistics about the bulk operation
106
- def execute
107
- ops = Enumerator.new do |yielder|
108
- @client.scan(@query, @params.merge(:_source => false)).each_document do |hit|
109
- yielder.yield([:delete, hit.select { |key, _| ["_index", "_type", "_id", "_routing"].include?(key) }])
110
- end
111
- end
112
-
113
- stats = @client.bulk_stream_items(ops, @params) { |item| accumulate(item) }
114
- @response_stats["took"] = stats["took"]
115
- @response_stats
116
- end
117
-
118
- end # DeleteByQuery
119
- end # Client
120
- end # Elastomer
12
+ end
13
+ end
@@ -40,7 +40,9 @@ module Elastomer
40
40
  # There are several other document attributes that control how
41
41
  # Elasticsearch will index the document. They are listed below. Please
42
42
  # refer to the Elasticsearch documentation for a full explanation of each
43
- # and how it affects the indexing process.
43
+ # and how it affects the indexing process. These indexing directives vary
44
+ # by Elasticsearch version. Attempting to use a directive not supported
45
+ # by the Elasticsearch server will raise an exception.
44
46
  #
45
47
  # :_id
46
48
  # :_type
@@ -49,12 +51,18 @@ module Elastomer
49
51
  # :_op_type
50
52
  # :_routing
51
53
  # :_parent
52
- # :_timestamp
53
- # :_ttl
54
- # :_consistency
55
- # :_replication
56
54
  # :_refresh
57
55
  #
56
+ # Elasticsearch 2.X only:
57
+ #
58
+ # :_timestamp (deprecated)
59
+ # :_ttl (deprecated)
60
+ # :_consistency
61
+ #
62
+ # Elasticsearch 5.x only:
63
+ #
64
+ # :_wait_for_active_shards
65
+ #
58
66
  # If any of these attributes are present in the document they will be
59
67
  # removed from the document before it is indexed. This means that the
60
68
  # document will be modified by this method.
@@ -65,6 +73,9 @@ module Elastomer
65
73
  # See https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html
66
74
  #
67
75
  # Returns the response body as a Hash
76
+ #
77
+ # Raises Elastomer::Client::IllegalArgument if an unsupported indexing
78
+ # directive is used.
68
79
  def index( document, params = {} )
69
80
  overrides = from_document document
70
81
  params = update_params(params, overrides)
@@ -246,19 +257,45 @@ module Elastomer
246
257
  response.body
247
258
  end
248
259
 
249
- # Delete documents from one or more indices and one or more types based
260
+ # Delete documents by query following either the native or
261
+ # application-level delete by query method.
262
+ #
263
+ # NOTE: The parameters and response format varies by version. To have more
264
+ # control over this, use app_delete_by_query or native_delete_by_query
265
+ # directly.
266
+ def delete_by_query(query, params = nil)
267
+ send(@client.version_support.delete_by_query_method, query, params)
268
+ end
269
+
270
+ # DEPRECATED: Delete documents from one or more indices and one or more types based
250
271
  # on a query. This method supports both the "request body" query and the
251
272
  # "URI request" query. When using the request body semantics, the query
252
273
  # hash must contain the :query key. Otherwise we assume a URI request is
253
274
  # being made.
254
275
  #
255
- # See Client#delete_by_query for more information.
276
+ # See Client#app_delete_by_query for more information.
256
277
  #
257
278
  # Returns a Hash of statistics about the delete operations
258
- def delete_by_query(query, params = nil)
279
+ def app_delete_by_query(query, params = nil)
259
280
  query, params = extract_params(query) if params.nil?
260
281
 
261
- @client.delete_by_query(query, update_params(params))
282
+ @client.app_delete_by_query(query, update_params(params))
283
+ end
284
+
285
+ # Delete documents from one or more indices and one or more types based
286
+ # on a query using Elasticsearch's _delete_by_query API.
287
+ #
288
+ # See Client#native_delete_by_query for more information.
289
+ #
290
+ # Returns a Hash of statistics about the delete operations as returned by
291
+ # _delete_by_query.
292
+ #
293
+ # Raises Elastomer::Client::IncompatibleVersionException if this version
294
+ # of Elasticsearch does not support _delete_by_query.
295
+ def native_delete_by_query(query, params = {})
296
+ query, params = extract_params(query) if params.nil?
297
+
298
+ @client.native_delete_by_query(query, update_params(params))
262
299
  end
263
300
 
264
301
  # Matches a provided or existing document to the stored percolator
@@ -517,9 +554,6 @@ Percolate
517
554
  client.multi_percolate(params, &block)
518
555
  end
519
556
 
520
- SPECIAL_KEYS = %w[index type id version version_type op_type routing parent timestamp ttl consistency replication refresh].freeze
521
- SPECIAL_KEYS_HASH = SPECIAL_KEYS.inject({}) { |h, k| h[k.to_sym] = "_#{k}"; h }.freeze
522
-
523
557
  # Internal: Given a `document` generate an options hash that will
524
558
  # override parameters based on the content of the document. The document
525
559
  # will be returned as the value of the :body key.
@@ -530,14 +564,30 @@ Percolate
530
564
  # document - A document Hash or JSON encoded String.
531
565
  #
532
566
  # Returns an options Hash extracted from the document.
567
+ #
568
+ # Raises Elastomer::Client::IllegalArgument if an unsupported indexing
569
+ # directive is used.
533
570
  def from_document( document )
534
571
  opts = {:body => document}
535
572
 
536
573
  if document.is_a? Hash
537
- SPECIAL_KEYS_HASH.each do |key, field|
574
+ client.version_support.indexing_directives.each do |key, field|
538
575
  opts[key] = document.delete field if document.key? field
539
576
  opts[key] = document.delete field.to_sym if document.key? field.to_sym
540
577
  end
578
+
579
+ # COMPATIBILITY
580
+ # Fail fast if a consumer is attempting to use an indexing parameter
581
+ # for a different version of Elasticsearch. Elasticsearch 5+ is strict
582
+ # about parameter names so we need to either ignore these (in which
583
+ # case they would be indexed with the document) or fail-fast.
584
+ # Elasticsearch 2.X will happily ignore unknown parameters, but we
585
+ # felt it was best to consistently fail fast.
586
+ client.version_support.unsupported_indexing_directives.each do |key, field|
587
+ if document.key?(field) || document.key?(field.to_sym)
588
+ raise IllegalArgument, "Elasticsearch #{client.version} does not support the '#{key}' indexing parameter"
589
+ end
590
+ end
541
591
  end
542
592
 
543
593
  opts
@@ -104,5 +104,13 @@ module Elastomer
104
104
  ::Elastomer::Client.const_set(error_name, Class.new(Error))
105
105
  end
106
106
 
107
- end # Client
108
- end # Elastomer
107
+ # Exception for operations that are unsupported with the version of
108
+ # Elasticsearch being used.
109
+ IncompatibleVersionException = Class.new Error
110
+
111
+ # Exception for client-detected and server-raised invalid Elasticsearch
112
+ # request parameter.
113
+ IllegalArgument = Class.new Error
114
+
115
+ end
116
+ end
@@ -230,7 +230,8 @@ module Elastomer
230
230
  #
231
231
  # Returns the response body as a Hash
232
232
  def analyze( text, params = {} )
233
- response = client.get "{/index}/_analyze", update_params(params, :body => text.to_s, :action => "index.analyze")
233
+ body = text.is_a?(Hash) ? text : {text: text.to_s}
234
+ response = client.get "{/index}/_analyze", update_params(params, body: body, action: "index.analyze")
234
235
  response.body
235
236
  end
236
237
 
@@ -261,19 +262,21 @@ module Elastomer
261
262
  response.body
262
263
  end
263
264
 
264
- # Optimize one or more indices. Optimizing an index allows for faster
265
- # search operations but can be resource intensive.
265
+ # Force merge one or more indices. Force merging an index allows to
266
+ # reduce the number of segments but can be resource intensive.
266
267
  #
267
268
  # params - Parameters Hash
268
- # :index - set to "_all" to optimize all indices
269
+ # :index - set to "_all" to force merge all indices
269
270
  #
270
- # See https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-optimize.html
271
+ # See https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html
271
272
  #
272
273
  # Returns the response body as a Hash
273
- def optimize( params = {} )
274
- response = client.post "{/index}/_optimize", update_params(params, :action => "index.optimize")
274
+ def forcemerge( params = {} )
275
+ response = client.post "{/index}/_forcemerge", update_params(params, :action => "index.forcemerge")
275
276
  response.body
276
277
  end
278
+ # DEPRECATED: ES 5.X has removed the `/_optimize` endpoint.
279
+ alias_method :optimize, :forcemerge
277
280
 
278
281
  # Provides insight into ongoing index shard recoveries. Recovery status
279
282
  # may be reported for specific indices, or cluster-wide.
@@ -523,14 +526,39 @@ module Elastomer
523
526
  Warmer.new(client, name, warmer_name)
524
527
  end
525
528
 
529
+ # Delete documents by query following either the native or
530
+ # application-level delete by query method.
531
+ #
532
+ # NOTE: The parameters and response format varies by version. To have more
533
+ # control over this, use app_delete_by_query or native_delete_by_query
534
+ # directly.
535
+ def delete_by_query(query, params = nil)
536
+ docs.send(client.version_support.delete_by_query_method, query, params)
537
+ end
538
+
539
+ # DEPRECATED: Delete documents from one or more indices and one or more types based
540
+ # on a query using application-level logic.
541
+ #
542
+ # See Client#app_delete_by_query for more information.
543
+ #
544
+ # Returns a Hash of statistics about the delete operations simulating the
545
+ # Elasticsearch 2.x delete by query plugin's output.
546
+ def app_delete_by_query(query, params = nil)
547
+ docs.app_delete_by_query(query, params)
548
+ end
549
+
526
550
  # Delete documents from one or more indices and one or more types based
527
- # on a query.
551
+ # on a query using Elasticsearch's _delete_by_query API.
528
552
  #
529
- # See Client#delete_by_query for more information.
553
+ # See Client#native_delete_by_query for more information.
530
554
  #
531
- # Returns a Hash of statistics about the delete operations
532
- def delete_by_query(query, params = nil)
533
- docs.delete_by_query(query, params)
555
+ # Returns a Hash of statistics about the delete operations as returned by
556
+ # _delete_by_query.
557
+ #
558
+ # Raises Elastomer::Client::IncompatibleVersionException if this version
559
+ # of Elasticsearch does not support _delete_by_query.
560
+ def native_delete_by_query(query, params = nil)
561
+ docs.native_delete_by_query(query, params)
534
562
  end
535
563
 
536
564
  # Constructs a Percolator with the given id on this index.
@@ -80,7 +80,7 @@ module Elastomer
80
80
  #
81
81
  # Returns this MultiPercolate instance.
82
82
  def percolate(doc, header = {})
83
- add_to_actions(:percolate => header)
83
+ add_to_actions(:percolate => @params.merge(header))
84
84
  add_to_actions(:doc => doc)
85
85
  end
86
86
 
@@ -93,7 +93,7 @@ module Elastomer
93
93
  #
94
94
  # Returns this MultiPercolate instance.
95
95
  def count(doc, header = {})
96
- add_to_actions(:count => header)
96
+ add_to_actions(:count => @params.merge(header))
97
97
  add_to_actions(:doc => doc)
98
98
  end
99
99
 
@@ -0,0 +1,60 @@
1
+ module Elastomer
2
+ class Client
3
+ # Delete documents based on a query using the Elasticsearch _delete_by_query API.
4
+ #
5
+ # query - The query body as a Hash
6
+ # params - Parameters Hash
7
+ #
8
+ # Examples
9
+ #
10
+ # # request body query
11
+ # native_delete_by_query({:query => {:match_all => {}}}, :type => 'tweet')
12
+ #
13
+ # See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/docs-delete-by-query.html
14
+ #
15
+ # Returns a Hash containing the _delete_by_query response body.
16
+ def native_delete_by_query(query, parameters = {})
17
+ NativeDeleteByQuery.new(self, query, parameters).execute
18
+ end
19
+
20
+ class NativeDeleteByQuery
21
+ attr_reader :client, :query, :parameters
22
+
23
+ PARAMETERS = %i[
24
+ conflicts
25
+ index
26
+ q
27
+ refresh
28
+ routing
29
+ scroll_size
30
+ timeout
31
+ type
32
+ wait_for_active_shards
33
+ wait_for_completion
34
+ ].to_set.freeze
35
+
36
+ def initialize(client, query, parameters)
37
+ unless client.version_support.native_delete_by_query?
38
+ raise IncompatibleVersionException, "Elasticsearch '#{client.version}' does not support _delete_by_query"
39
+ end
40
+
41
+ parameters.keys.each do |key|
42
+ unless PARAMETERS.include?(key) || PARAMETERS.include?(key.to_sym)
43
+ raise IllegalArgument, "'#{key}' is not a valid _delete_by_query parameter"
44
+ end
45
+ end
46
+
47
+ @client = client
48
+ @query = query
49
+ @parameters = parameters
50
+
51
+ end
52
+
53
+ def execute
54
+ # TODO: Require index parameter. type is optional.
55
+ response = client.post("/{index}{/type}/_delete_by_query", parameters.merge(body: query))
56
+ response.body
57
+ end
58
+ end
59
+ end
60
+ end
@@ -12,6 +12,9 @@ module Elastomer
12
12
  @client = client
13
13
  @index_name = client.assert_param_presence(index_name, "index name")
14
14
  @id = client.assert_param_presence(id, "id")
15
+
16
+ # COMPATIBILITY
17
+ @percolator_type = client.version_support.percolator_type
15
18
  end
16
19
 
17
20
  attr_reader :client, :index_name, :id
@@ -25,7 +28,7 @@ module Elastomer
25
28
  #
26
29
  # Returns the response body as a Hash
27
30
  def create(body, params = {})
28
- response = client.put("/{index}/.percolator/{id}", defaults.merge(params.merge(:body => body, :action => "percolator.create")))
31
+ response = client.put("/{index}/#{@percolator_type}/{id}", defaults.merge(params.merge(:body => body, :action => "percolator.create")))
29
32
  response.body
30
33
  end
31
34
 
@@ -38,7 +41,7 @@ module Elastomer
38
41
  #
39
42
  # Returns the response body as a Hash
40
43
  def get(params = {})
41
- response = client.get("/{index}/.percolator/{id}", defaults.merge(params.merge(:action => "percolator.get")))
44
+ response = client.get("/{index}/#{@percolator_type}/{id}", defaults.merge(params.merge(:action => "percolator.get")))
42
45
  response.body
43
46
  end
44
47
 
@@ -51,7 +54,7 @@ module Elastomer
51
54
  #
52
55
  # Returns the response body as a Hash
53
56
  def delete(params = {})
54
- response = client.delete("/{index}/.percolator/{id}", defaults.merge(params.merge(:action => "percolator.delete")))
57
+ response = client.delete("/{index}/#{@percolator_type}/{id}", defaults.merge(params.merge(:action => "percolator.delete")))
55
58
  response.body
56
59
  end
57
60