elastomer-client 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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