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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: efb8ef5cb70b5ee2dff9d968876ec9b2412650f2
4
- data.tar.gz: ef5a5fab5b54be5581cddef9e0741f0cae962341
3
+ metadata.gz: 7db106056a2ea4d14012e82621bc4c4073c85c15
4
+ data.tar.gz: ed047e4e19b8f45e7c553998cb0935ea59c1a94e
5
5
  SHA512:
6
- metadata.gz: d3d7c0fe1ef38202642a185670511ad10e99bacd8a57c8f02ab03adefa0133fad0485511cb9a7d70251533b58031987454686783930340060dc641914c21a05d
7
- data.tar.gz: b2f69a64ad1e5c6de4f2a6ded6c00f4dbf3c639340bab1003cd7ad9fc80cfef8522df9bfe0be5c9f1e27fb00f7295c194f331c83228c2881ea60f36bdd99eb5f
6
+ metadata.gz: cb92e602580ce508195626093783510df3320ba20416cac7c879fcf80839ce87fbb64dd0af7c8f37b0a2775cf9f0c3d603a692251b8ab9c7c87bcfe867b955a4
7
+ data.tar.gz: 423da9494641de0a33a90dc26fdae67564469291a1120202015a5f9daecab22da06bbbf9c1e351a41a825b031563e90066ba9585b483d5c257ad3044c64ba449
data/.gitignore CHANGED
@@ -8,3 +8,5 @@
8
8
  Gemfile.lock
9
9
  *.gem
10
10
  tags
11
+ .byebug_history
12
+ *.swp
@@ -6,10 +6,6 @@ env:
6
6
  - ES_VERSION=2.3.5 ES_DOWNLOAD_URL=https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/${ES_VERSION}/elasticsearch-${ES_VERSION}.tar.gz
7
7
  - ES_VERSION=5.6.4 ES_DOWNLOAD_URL=https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ES_VERSION}.tar.gz
8
8
 
9
- matrix:
10
- allow_failures:
11
- - env: ES_VERSION=5.6.4 ES_DOWNLOAD_URL=https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ES_VERSION}.tar.gz
12
-
13
9
  # Configure a specific version of Elasticsearch
14
10
  # See: https://docs.travis-ci.com/user/database-setup/#Installing-ElasticSearch-on-trusty-container-based-infrastructure
15
11
  install:
@@ -1,3 +1,11 @@
1
+ ## 3.0.0 (2017-12-15)
2
+ - Fixed swapped args in {Client,Index}#multi\_percolate count calls using block API
3
+ - Support for Elasticsearch 5.x
4
+ - Uses Elasticsearch's built-in `_delete_by_query` when supported
5
+ - GET and HEAD requests are retried when possible
6
+ - Add support for `_tasks` API
7
+ - Replace `scan` queries with `scroll` sorted by `doc_id`
8
+
1
9
  ## 2.3.0 (2017-11-29)
2
10
  - Remove Elasticsearch 1.x and earlier code paths
3
11
  - Fix CI and configure an Elasticsearch 5.6 build
@@ -0,0 +1,8 @@
1
+ version: "2.1"
2
+
3
+ networks:
4
+ default:
5
+ ipam:
6
+ config:
7
+ - subnet: 192.168.0.0/26
8
+ gateway: 192.168.0.1
@@ -0,0 +1,34 @@
1
+ version: "2.1"
2
+
3
+ services:
4
+ elasticsearch2.4:
5
+ image: elasticsearch:2.4.6
6
+ container_name: es2.4
7
+ environment:
8
+ - cluster.name=elastomer2.4
9
+ - bootstrap.memory_lock=true
10
+ - discovery.type=single-node
11
+ - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
12
+ ulimits:
13
+ memlock:
14
+ soft: -1
15
+ hard: -1
16
+ nofile:
17
+ soft: 65536
18
+ hard: 65536
19
+ mem_limit: 1g
20
+ cap_add:
21
+ - IPC_LOCK
22
+ volumes:
23
+ - esrepos:/usr/share/elasticsearch/repos
24
+ - ./elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
25
+ ports:
26
+ - 127.0.0.1:19200:9200
27
+
28
+ volumes:
29
+ esrepos:
30
+ driver: local
31
+ driver_opts:
32
+ device: tmpfs
33
+ type: tmpfs
34
+ o: size=100m,uid=102,gid=102
@@ -0,0 +1,37 @@
1
+ version: "2.1"
2
+
3
+ services:
4
+ elasticsearch5.6:
5
+ image: docker.elastic.co/elasticsearch/elasticsearch:5.6.4
6
+ container_name: es5.6
7
+ environment:
8
+ - cluster.name=elastomer5.6
9
+ - bootstrap.memory_lock=true
10
+ - discovery.type=single-node
11
+ - xpack.monitoring.enabled=false
12
+ - xpack.security.enabled=false
13
+ - xpack.watcher.enabled=false
14
+ - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
15
+ ulimits:
16
+ memlock:
17
+ soft: -1
18
+ hard: -1
19
+ nofile:
20
+ soft: 65536
21
+ hard: 65536
22
+ mem_limit: 1g
23
+ cap_add:
24
+ - IPC_LOCK
25
+ volumes:
26
+ - esrepos:/usr/share/elasticsearch/repos
27
+ - ./elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
28
+ ports:
29
+ - 127.0.0.1:19200:9200
30
+
31
+ volumes:
32
+ esrepos:
33
+ driver: local
34
+ driver_opts:
35
+ device: tmpfs
36
+ type: tmpfs
37
+ o: size=100m,uid=102,gid=102
@@ -0,0 +1,15 @@
1
+ cluster.name: "docker-cluster"
2
+
3
+ network.host: 0.0.0.0
4
+
5
+ discovery.zen.minimum_master_nodes: 1
6
+
7
+ path:
8
+ data: /usr/share/elasticsearch/data
9
+ logs: /usr/share/elasticsearch/logs
10
+ repo: /usr/share/elasticsearch/repos
11
+
12
+ transport.tcp.port: 9300
13
+ http.port: 9200
14
+ http.max_content_length: 50mb
15
+
@@ -137,8 +137,8 @@ Let's take a look at some simple event log maintenance using elastomer-client.
137
137
  # the previous month's event log
138
138
  index = client.index "event-log-2014-09"
139
139
 
140
- # optimize the index to have only 1 segment file (expunges deleted documents)
141
- index.optimize \
140
+ # force merge the index to have only 1 segment file (expunges deleted documents)
141
+ index.force merge \
142
142
  :max_num_segments => 1,
143
143
  :wait_for_merge => true
144
144
 
@@ -63,7 +63,7 @@ The event namespace is `request.client.elastomer`.
63
63
  - index.get_mapping
64
64
  - index.get_settings
65
65
  - index.open
66
- - index.optimize
66
+ - index.forcemerge
67
67
  - index.recovery
68
68
  - index.refresh
69
69
  - index.segments
@@ -28,7 +28,9 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency "activesupport", ">= 3.0"
29
29
  spec.add_development_dependency "minitest", "~> 5.10"
30
30
  spec.add_development_dependency "minitest-fail-fast", "~> 0.1.0"
31
+ spec.add_development_dependency "minitest-focus", "~> 1.1", ">= 1.1.2"
31
32
  spec.add_development_dependency "webmock", "~> 2.3"
32
33
  spec.add_development_dependency "awesome_print", "~> 1.8"
34
+ spec.add_development_dependency "pry-byebug", "~> 3.4"
33
35
  spec.add_development_dependency "rake"
34
36
  end
@@ -4,16 +4,23 @@ require "multi_json"
4
4
  require "semantic"
5
5
 
6
6
  require "elastomer/version"
7
+ require "elastomer/version_support"
7
8
 
8
9
  module Elastomer
9
10
 
10
11
  class Client
11
12
  MAX_REQUEST_SIZE = 250 * 2**20 # 250 MB
12
13
 
14
+ RETRYABLE_METHODS = %i[get head].freeze
15
+
13
16
  # Create a new client that can be used to make HTTP requests to the
14
- # Elasticsearch server.
17
+ # Elasticsearch server. If you use `:max_retries`, then any GET or HEAD
18
+ # request will be retried up to this many times with a 75ms delay between
19
+ # the retry attempts. Only non-fatal exceptions will retried automatically.
20
+ #
21
+ # see lib/elastomer/client/errors.rb#L92-L94
15
22
  #
16
- # opts - The options Hash
23
+ # Method params:
17
24
  # :host - the host as a String
18
25
  # :port - the port number of the server
19
26
  # :url - the URL as a String (overrides :host and :port)
@@ -22,25 +29,43 @@ module Elastomer
22
29
  # :adapter - the Faraday adapter to use (defaults to :excon)
23
30
  # :opaque_id - set to `true` to use the 'X-Opaque-Id' request header
24
31
  # :max_request_size - the maximum allowed request size in bytes (defaults to 250 MB)
32
+ # :max_retries - the maximum number of request retires (defaults to 0)
33
+ # :retry_delay - delay in seconds between retries (defaults to 0.075)
25
34
  #
26
- def initialize( opts = {} )
27
- host = opts.fetch :host, "localhost"
28
- port = opts.fetch :port, 9200
29
- @url = opts.fetch :url, "http://#{host}:#{port}"
35
+ def initialize(host: "localhost", port: 9200, url: nil,
36
+ read_timeout: 5, open_timeout: 2, max_retries: 0, retry_delay: 0.075,
37
+ opaque_id: false, adapter: Faraday.default_adapter, max_request_size: MAX_REQUEST_SIZE)
38
+
39
+ @url = url || "http://#{host}:#{port}"
30
40
 
31
41
  uri = Addressable::URI.parse @url
32
42
  @host = uri.host
33
43
  @port = uri.port
34
44
 
35
- @read_timeout = opts.fetch :read_timeout, 5
36
- @open_timeout = opts.fetch :open_timeout, 2
37
- @adapter = opts.fetch :adapter, Faraday.default_adapter
38
- @opaque_id = opts.fetch :opaque_id, false
39
- @max_request_size = opts.fetch :max_request_size, MAX_REQUEST_SIZE
45
+ @read_timeout = read_timeout
46
+ @open_timeout = open_timeout
47
+ @max_retries = max_retries
48
+ @retry_delay = retry_delay
49
+ @adapter = adapter
50
+ @opaque_id = opaque_id
51
+ @max_request_size = max_request_size
40
52
  end
41
53
 
42
54
  attr_reader :host, :port, :url
43
- attr_reader :read_timeout, :open_timeout, :max_request_size
55
+ attr_reader :read_timeout, :open_timeout
56
+ attr_reader :max_retries, :retry_delay, :max_request_size
57
+
58
+ # Returns a duplicate of this Client connection configured in the exact same
59
+ # fashion.
60
+ def dup
61
+ self.class.new \
62
+ url: url,
63
+ read_timeout: read_timeout,
64
+ open_timeout: open_timeout,
65
+ adapter: @adapter,
66
+ opaque_id: @opaque_id,
67
+ max_request_size: max_request_size
68
+ end
44
69
 
45
70
  # Returns true if the server is available; returns false otherwise.
46
71
  def ping
@@ -90,6 +115,15 @@ module Elastomer
90
115
  end
91
116
  end
92
117
 
118
+ # Internal: Reset the client by removing the current Faraday::Connection. A
119
+ # new connection will be established on the next request.
120
+ #
121
+ # Returns this Client instance.
122
+ def reset!
123
+ @connection = nil
124
+ self
125
+ end
126
+
93
127
  # Internal: Sends an HTTP HEAD request to the server.
94
128
  #
95
129
  # path - The path as a String
@@ -153,15 +187,18 @@ module Elastomer
153
187
  # params - Parameters Hash
154
188
  # :body - Will be used as the request body
155
189
  # :read_timeout - Optional read timeout (in seconds) for the request
190
+ # :max_retires - Optional retry number for the request
156
191
  #
157
192
  # Returns a Faraday::Response
158
193
  # Raises an Elastomer::Client::Error on 4XX and 5XX responses
159
194
  # rubocop:disable Metrics/MethodLength
160
195
  def request( method, path, params )
161
- read_timeout = params.delete :read_timeout
162
- body = extract_body params
163
- path = expand_path path, params
196
+ read_timeout = params.delete(:read_timeout)
197
+ request_max_retries = params.delete(:max_retries) || max_retries
198
+ body = extract_body(params)
199
+ path = expand_path(path, params)
164
200
 
201
+ params[:retries] = retries = 0
165
202
  instrument(path, body, params) do
166
203
  begin
167
204
  response =
@@ -195,14 +232,35 @@ module Elastomer
195
232
 
196
233
  # wrap Faraday errors with appropriate Elastomer::Client error classes
197
234
  rescue Faraday::Error::ClientError => boom
198
- error_name = boom.class.name.split("::").last
199
- error_class = Elastomer::Client.const_get(error_name) rescue Elastomer::Client::Error
200
- raise error_class.new(boom, method.upcase, path)
235
+ error = wrap_faraday_error(boom, method, path)
236
+ if error.retry? && RETRYABLE_METHODS.include?(method) && (retries += 1) <= request_max_retries
237
+ params[:retries] = retries
238
+ sleep retry_delay
239
+ retry
240
+ end
241
+ raise error
242
+ rescue OpaqueIdError => boom
243
+ reset!
244
+ raise boom
201
245
  end
202
246
  end
203
247
  end
204
248
  # rubocop:enable Metrics/MethodLength
205
249
 
250
+ # Internal: Returns a new Elastomer::Client error that wraps the given
251
+ # Faraday error. A generic Error is returned if we cannot wrap the given
252
+ # Faraday error.
253
+ #
254
+ # error - The Faraday error
255
+ # method - The request method
256
+ # path - The request path
257
+ #
258
+ def wrap_faraday_error(error, method, path)
259
+ error_name = error.class.name.split("::").last
260
+ error_class = Elastomer::Client.const_get(error_name) rescue Elastomer::Client::Error
261
+ error_class.new(error, method.upcase, path)
262
+ end
263
+
206
264
  # Internal: Extract the :body from the params Hash and convert it to a
207
265
  # JSON String format. If the params Hash does not contain a :body then no
208
266
  # action is taken and `nil` is returned.
@@ -263,6 +321,7 @@ module Elastomer
263
321
  query_values = params.dup
264
322
  query_values.delete :action
265
323
  query_values.delete :context
324
+ query_values.delete :retries
266
325
 
267
326
  template.keys.map(&:to_sym).each do |key|
268
327
  value = query_values.delete key
@@ -305,21 +364,11 @@ module Elastomer
305
364
  raise ServerError, response if response.status >= 500
306
365
 
307
366
  if response.body.is_a?(Hash) && (error = response.body["error"])
308
- # ES 2.X style
309
- if error.is_a?(Hash)
310
- root_cause = Array(error["root_cause"]).first || error
311
- case root_cause["type"]
312
- when "index_not_found_exception"; raise IndexNotFoundError, response
313
- when "query_parsing_exception"; raise QueryParsingError, response
314
- end
315
-
316
- # ES 1.X style
317
- elsif error.is_a?(String)
318
- case error
319
- when %r/IndexMissingException/; raise IndexNotFoundError, response
320
- when %r/QueryParsingException/; raise QueryParsingError, response
321
- when %r/ParseException/; raise QueryParsingError, response
322
- end
367
+ root_cause = Array(error["root_cause"]).first || error
368
+ case root_cause["type"]
369
+ when "index_not_found_exception"; raise IndexNotFoundError, response
370
+ when "illegal_argument_exception"; raise IllegalArgument, response
371
+ when *version_support.query_parse_exception; raise QueryParsingError, response
323
372
  end
324
373
 
325
374
  raise RequestError, response
@@ -361,6 +410,10 @@ module Elastomer
361
410
  end
362
411
  end
363
412
 
413
+ def version_support
414
+ @version_support ||= VersionSupport.new(version)
415
+ end
416
+
364
417
  end # Client
365
418
  end # Elastomer
366
419
 
@@ -0,0 +1,158 @@
1
+ module Elastomer
2
+ class Client
3
+
4
+ # DEPRECATED: Delete documents from one or more indices and one or more
5
+ # types based on a query.
6
+ #
7
+ # The return value follows the format returned by the Elasticsearch Delete
8
+ # by Query plugin: https://github.com/elastic/elasticsearch/blob/v2.4.6/docs/plugins/delete-by-query.asciidoc
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
+ # For native _delete_by_query functionality in Elasticsearch 5+, see the
15
+ # native_delete_by_query method.
16
+ #
17
+ # query - The query body as a Hash
18
+ # params - Parameters Hash
19
+ #
20
+ # Examples
21
+ #
22
+ # # request body query
23
+ # app_delete_by_query({:query => {:match_all => {}}}, :type => 'tweet')
24
+ #
25
+ # # same thing but using the URI request method
26
+ # app_delete_by_query(nil, { :q => '*:*', :type => 'tweet' })
27
+ #
28
+ # See https://www.elastic.co/guide/en/elasticsearch/plugins/current/delete-by-query-usage.html
29
+ #
30
+ # Returns a Hash of statistics about the delete operations, for example:
31
+ #
32
+ # {
33
+ # "took" : 639,
34
+ # "_indices" : {
35
+ # "_all" : {
36
+ # "found" : 5901,
37
+ # "deleted" : 5901,
38
+ # "missing" : 0,
39
+ # "failed" : 0
40
+ # },
41
+ # "twitter" : {
42
+ # "found" : 5901,
43
+ # "deleted" : 5901,
44
+ # "missing" : 0,
45
+ # "failed" : 0
46
+ # }
47
+ # },
48
+ # "failures" : [ ]
49
+ # }
50
+ def app_delete_by_query(query, params = {})
51
+ AppDeleteByQuery.new(self, query, params).execute
52
+ end
53
+
54
+ class AppDeleteByQuery
55
+
56
+ SEARCH_PARAMETERS = %i[
57
+ index
58
+ type
59
+ q
60
+ df
61
+ analyzer
62
+ analyze_wildcard
63
+ batched_reduce_size
64
+ default_operator
65
+ lenient
66
+ explain
67
+ _source
68
+ stored_fields
69
+ sort
70
+ track_scores
71
+ timeout
72
+ terminate_after
73
+ from
74
+ size
75
+ search_type
76
+ scroll
77
+ ].to_set.freeze
78
+
79
+
80
+ # Create a new DeleteByQuery command for deleting documents matching a
81
+ # query
82
+ #
83
+ # client - Elastomer::Client used for HTTP requests to the server
84
+ # query - The query used to find documents to delete
85
+ # params - Other URL parameters
86
+ def initialize(client, query, params = {})
87
+ @client = client
88
+ @query = query
89
+ @params = params
90
+ @response_stats = { "took" => 0, "_indices" => { "_all" => {} }, "failures" => [] }
91
+ end
92
+
93
+ attr_reader :client, :query, :params, :response_stats
94
+
95
+ # Internal: Determine whether or not an HTTP status code is in the range
96
+ # 200 to 299
97
+ #
98
+ # status - HTTP status code
99
+ #
100
+ # Returns a boolean
101
+ def is_ok?(status)
102
+ status.between?(200, 299)
103
+ end
104
+
105
+ # Internal: Tally the contributions of an item to the found, deleted,
106
+ # missing, and failed counts for the summary statistics
107
+ #
108
+ # item - An element of the items array from a bulk response
109
+ #
110
+ # Returns a Hash of counts for each category
111
+ def categorize(item)
112
+ {
113
+ "found" => item["found"] || item["status"] == 409 ? 1 : 0,
114
+ "deleted" => is_ok?(item["status"]) ? 1 : 0,
115
+ "missing" => !item["found"] && !item.key?("error") ? 1 : 0,
116
+ "failed" => item.key?("error") ? 1 : 0,
117
+ }
118
+ end
119
+
120
+ # Internal: Combine a response item with the existing statistics
121
+ #
122
+ # item - A bulk response item
123
+ def accumulate(item)
124
+ item = item["delete"]
125
+ (@response_stats["_indices"][item["_index"]] ||= {}).merge!(categorize(item)) { |_, n, m| n + m }
126
+ @response_stats["_indices"]["_all"].merge!(categorize(item)) { |_, n, m| n + m }
127
+ @response_stats["failures"] << item unless is_ok? item["status"]
128
+ end
129
+
130
+ # Perform the Delete by Query action
131
+ #
132
+ # Returns a Hash of statistics about the bulk operation
133
+ def execute
134
+ ops = Enumerator.new do |yielder|
135
+ @client.scan(@query, search_params).each_document do |hit|
136
+ yielder.yield([:delete, hit.select { |key, _| ["_index", "_type", "_id", "_routing"].include?(key) }])
137
+ end
138
+ end
139
+
140
+ stats = @client.bulk_stream_items(ops, bulk_params) { |item| accumulate(item) }
141
+ @response_stats["took"] = stats["took"]
142
+ @response_stats
143
+ end
144
+
145
+ # Internal: Remove parameters that are not valid for the _search endpoint
146
+ def search_params
147
+ params = @params.merge(:_source => false)
148
+ params.select {|p, _| SEARCH_PARAMETERS.include? p}
149
+ end
150
+
151
+ # Internal: Remove parameters that are not valid for the _bulk endpoint
152
+ def bulk_params
153
+ @params.clone.tap { |p| p.delete(:q) }
154
+ end
155
+
156
+ end # AppDeleteByQuery
157
+ end # Client
158
+ end # Elastomer