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
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