elastomer-client 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +4 -0
  5. data/Gemfile +5 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +108 -0
  8. data/Rakefile +9 -0
  9. data/docs/notifications.md +71 -0
  10. data/elastomer-client.gemspec +30 -0
  11. data/lib/elastomer/client.rb +307 -0
  12. data/lib/elastomer/client/bulk.rb +257 -0
  13. data/lib/elastomer/client/cluster.rb +208 -0
  14. data/lib/elastomer/client/docs.rb +432 -0
  15. data/lib/elastomer/client/errors.rb +51 -0
  16. data/lib/elastomer/client/index.rb +407 -0
  17. data/lib/elastomer/client/multi_search.rb +115 -0
  18. data/lib/elastomer/client/nodes.rb +87 -0
  19. data/lib/elastomer/client/scan.rb +161 -0
  20. data/lib/elastomer/client/template.rb +85 -0
  21. data/lib/elastomer/client/warmer.rb +96 -0
  22. data/lib/elastomer/core_ext/time.rb +7 -0
  23. data/lib/elastomer/middleware/encode_json.rb +51 -0
  24. data/lib/elastomer/middleware/opaque_id.rb +69 -0
  25. data/lib/elastomer/middleware/parse_json.rb +39 -0
  26. data/lib/elastomer/notifications.rb +83 -0
  27. data/lib/elastomer/version.rb +7 -0
  28. data/script/bootstrap +16 -0
  29. data/script/cibuild +28 -0
  30. data/script/console +9 -0
  31. data/script/testsuite +10 -0
  32. data/test/assertions.rb +74 -0
  33. data/test/client/bulk_test.rb +226 -0
  34. data/test/client/cluster_test.rb +113 -0
  35. data/test/client/docs_test.rb +394 -0
  36. data/test/client/index_test.rb +244 -0
  37. data/test/client/multi_search_test.rb +129 -0
  38. data/test/client/nodes_test.rb +35 -0
  39. data/test/client/scan_test.rb +84 -0
  40. data/test/client/stubbed_client_tests.rb +40 -0
  41. data/test/client/template_test.rb +33 -0
  42. data/test/client/warmer_test.rb +56 -0
  43. data/test/client_test.rb +86 -0
  44. data/test/core_ext/time_test.rb +46 -0
  45. data/test/middleware/encode_json_test.rb +53 -0
  46. data/test/middleware/opaque_id_test.rb +39 -0
  47. data/test/middleware/parse_json_test.rb +54 -0
  48. data/test/test_helper.rb +94 -0
  49. metadata +210 -0
@@ -0,0 +1,432 @@
1
+
2
+ module Elastomer
3
+ class Client
4
+
5
+ # Provides access to document-level API commands.
6
+ #
7
+ # name - The name of the index as a String
8
+ # type - The document type as a String
9
+ #
10
+ # Returns a Docs instance.
11
+ def docs( name, type = nil )
12
+ Docs.new self, name, type
13
+ end
14
+
15
+
16
+ class Docs
17
+ # Create a new document client for making API requests that pertain to
18
+ # the indexing and searching of documents in a search index.
19
+ #
20
+ # client - Elastomer::Client used for HTTP requests to the server
21
+ # name - The name of the index as a String
22
+ # type - The document type as a String
23
+ #
24
+ def initialize( client, name, type = nil )
25
+ @client = client
26
+ @name = @client.assert_param_presence(name, 'index name')
27
+ @type = @client.assert_param_presence(type, 'document type') unless type.nil?
28
+ end
29
+
30
+ attr_reader :client, :name, :type
31
+
32
+ # Adds or updates a document in the index, making it searchable.
33
+ # See http://www.elasticsearch.org/guide/reference/api/index_/
34
+ #
35
+ # document - The document (as a Hash or JSON encoded String) to add to the index
36
+ # params - Parameters Hash
37
+ #
38
+ # Returns the response body as a Hash
39
+ def index( document, params = {} )
40
+ overrides = from_document(document)
41
+ params = update_params(params, overrides)
42
+ params[:action] = 'docs.index'
43
+
44
+ params.delete(:id) if params[:id].nil? || params[:id].to_s =~ /\A\s*\z/
45
+
46
+ response =
47
+ if params[:id]
48
+ client.put '/{index}/{type}/{id}', params
49
+ else
50
+ client.post '/{index}/{type}', params
51
+ end
52
+
53
+ response.body
54
+ end
55
+ alias :add :index
56
+
57
+ # Delete a document from the index based on the document ID. The :id is
58
+ # provided as part of the params hash.
59
+ #
60
+ # See http://www.elasticsearch.org/guide/reference/api/delete/
61
+ #
62
+ # params - Parameters Hash
63
+ #
64
+ # Returns the response body as a Hash
65
+ def delete( params = {} )
66
+ response = client.delete '/{index}/{type}/{id}', update_params(params, :action => 'docs.delete')
67
+ response.body
68
+ end
69
+
70
+ # Retrieve a document from the index based on its ID. The :id is
71
+ # provided as part of the params hash.
72
+ #
73
+ # See http://www.elasticsearch.org/guide/reference/api/get/
74
+ #
75
+ # params - Parameters Hash
76
+ #
77
+ # Returns the response body as a Hash
78
+ def get( params = {} )
79
+ response = client.get '/{index}/{type}/{id}', update_params(params, :action => 'docs.get')
80
+ response.body
81
+ end
82
+
83
+ # Retrieve the document source from the index based on the ID and type.
84
+ # The :id is provided as part of the params hash.
85
+ #
86
+ # See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html#_source
87
+ #
88
+ # params - Parameters Hash
89
+ #
90
+ # Returns the response body as a Hash
91
+ def source( params = {} )
92
+ response = client.get '/{index}/{type}/{id}/_source', update_params(params, :action => 'docs.source')
93
+ response.body
94
+ end
95
+
96
+ # Allows to get multiple documents based on an index, type, and id (and possibly routing).
97
+ # See http://www.elasticsearch.org/guide/reference/api/multi-get/
98
+ #
99
+ # docs - The Hash describing the documents to get
100
+ # params - Parameters Hash
101
+ #
102
+ # Returns the response body as a Hash
103
+ def multi_get( docs, params = {} )
104
+ overrides = from_document(docs)
105
+ overrides[:action] = 'docs.multi_get'
106
+
107
+ response = client.get '{/index}{/type}{/id}/_mget', update_params(params, overrides)
108
+ response.body
109
+ end
110
+
111
+ # Update a document based on a script provided.
112
+ # See http://www.elasticsearch.org/guide/reference/api/update/
113
+ #
114
+ # script - The script (as a Hash) used to update the document in place
115
+ # params - Parameters Hash
116
+ #
117
+ # Returns the response body as a Hash
118
+ def update( script, params = {} )
119
+ overrides = from_document(script)
120
+ overrides[:action] = 'docs.update'
121
+
122
+ response = client.post '/{index}/{type}/{id}/_update', update_params(params, overrides)
123
+ response.body
124
+ end
125
+
126
+ # Allows you to execute a search query and get back search hits that
127
+ # match the query. This method supports both the "request body" query
128
+ # and the "URI request" query. When using the request body semantics,
129
+ # the query hash must contain the :query key. Otherwise we assume a URI
130
+ # request is being made.
131
+ #
132
+ # See http://www.elasticsearch.org/guide/reference/api/search/
133
+ #
134
+ # query - The query body as a Hash
135
+ # params - Parameters Hash
136
+ #
137
+ # Examples
138
+ #
139
+ # # request body query
140
+ # search({:query => {:match_all => {}}}, :type => 'tweet')
141
+ #
142
+ # # same thing but using the URI request method
143
+ # search(:q => '*:*', :type => 'tweet')
144
+ #
145
+ # Returns the response body as a hash
146
+ def search( query, params = nil )
147
+ query, params = extract_params(query) if params.nil?
148
+
149
+ response = client.get '/{index}{/type}/_search', update_params(params, :body => query, :action => 'docs.search')
150
+ response.body
151
+ end
152
+
153
+ # Executes a search query, but instead of returning results, returns
154
+ # the number of documents matched. This method supports both the
155
+ # "request body" query and the "URI request" query. When using the
156
+ # request body semantics, the query hash must contain the :query key.
157
+ # Otherwise we assume a URI request is being made.
158
+ #
159
+ # See http://www.elasticsearch.org/guide/reference/api/count/
160
+ #
161
+ # query - The query body as a Hash
162
+ # params - Parameters Hash
163
+ #
164
+ # Examples
165
+ #
166
+ # # request body query
167
+ # count({:match_all => {}}, :type => 'tweet')
168
+ #
169
+ # # same thing but using the URI request method
170
+ # count(:q => '*:*', :type => 'tweet')
171
+ #
172
+ # Returns the response body as a Hash
173
+ def count(query, params = nil)
174
+ query, params = extract_params(query) if params.nil?
175
+
176
+ response = client.get '/{index}{/type}/_count', update_params(params, :body => query)
177
+ response.body
178
+ end
179
+
180
+ # Delete documents from one or more indices and one or more types based
181
+ # on a query. This method supports both the "request body" query and the
182
+ # "URI request" query. When using the request body semantics, the query
183
+ # hash must contain the :query key. Otherwise we assume a URI request is
184
+ # being made.
185
+ #
186
+ # See http://www.elasticsearch.org/guide/reference/api/delete-by-query/
187
+ #
188
+ # query - The query body as a Hash
189
+ # params - Parameters Hash
190
+ #
191
+ # Examples
192
+ #
193
+ # # request body query
194
+ # delete_by_query({:query => {:match_all => {}}}, :type => 'tweet')
195
+ #
196
+ # # same thing but using the URI request method
197
+ # delete_by_query(:q => '*:*', :type => 'tweet')
198
+ #
199
+ # Returns the response body as a hash
200
+ def delete_by_query( query, params = nil )
201
+ query, params = extract_params(query) if params.nil?
202
+
203
+ response = client.delete '/{index}{/type}/_query', update_params(params, :body => query, :action => 'docs.delete_by_query')
204
+ response.body
205
+ end
206
+
207
+ =begin
208
+ Percolate
209
+ =end
210
+
211
+ # Search for documents similar to a specific document. The document
212
+ # :id is provided as part of the params hash. If the _all field is
213
+ # not enabled, :mlt_fields must be passed. A query cannot be present
214
+ # in the query body, but other fields like :size and :facets are
215
+ # allowed.
216
+ #
217
+ # See http://www.elasticsearch.org/guide/reference/api/more-like-this/
218
+ #
219
+ # params - Parameters Hash
220
+ #
221
+ # Examples
222
+ #
223
+ # more_like_this(:mlt_fields => "title", :min_term_freq => 1, :type => "doc1", :id => 1)
224
+ #
225
+ # # with query hash
226
+ # more_like_this({:from => 5, :size => 10}, :mlt_fields => "title",
227
+ # :min_term_freq => 1, :type => "doc1", :id => 1)
228
+ #
229
+ # Returns the response body as a hash
230
+ def more_like_this(query, params = nil)
231
+ query, params = extract_params(query) if params.nil?
232
+
233
+ response = client.get '/{index}/{type}/{id}/_mlt', update_params(params, :body => query, :action => 'docs.more_like_this')
234
+ response.body
235
+ end
236
+
237
+ # Compute a score explanation for a query and a specific document. This
238
+ # can give useful feedback about why a document matched or didn't match
239
+ # a query. The document :id is provided as part of the params hash.
240
+ #
241
+ # See http://www.elasticsearch.org/guide/reference/api/explain/
242
+ #
243
+ # query - The query body as a Hash
244
+ # params - Parameters Hash
245
+ #
246
+ # Examples
247
+ #
248
+ # explain({:query => {:term => {"message" => "search"}}}, :id => 1)
249
+ #
250
+ # explain(:q => "message:search", :id => 1)
251
+ #
252
+ # Returns the response body as a hash
253
+ def explain(query, params = nil)
254
+ query, params = extract_params(query) if params.nil?
255
+
256
+ response = client.get '/{index}/{type}/{id}/_explain', update_params(params, :body => query, :action => 'docs.explain')
257
+ response.body
258
+ end
259
+
260
+ # Validate a potentially expensive query before running it. The
261
+ # :explain parameter can be used to get detailed information about
262
+ # why a query failed.
263
+ #
264
+ # See http://www.elasticsearch.org/guide/reference/api/validate/
265
+ #
266
+ # query - The query body as a Hash
267
+ # params - Parameters Hash
268
+ #
269
+ # Examples
270
+ #
271
+ # # request body query
272
+ # validate(:query_string => {:query => "*:*"})
273
+ #
274
+ # # same thing but using the URI query parameter
275
+ # validate({:q => "post_date:foo"}, :explain => true)
276
+ #
277
+ # Returns the response body as a hash
278
+ def validate(query, params = nil)
279
+ query, params = extract_params(query) if params.nil?
280
+
281
+ response = client.get '/{index}{/type}/_validate/query', update_params(params, :body => query, :action => 'docs.validate')
282
+ response.body
283
+ end
284
+
285
+ # Perform bulk indexing and/or delete operations. The current index name
286
+ # and document type will be passed to the bulk API call as part of the
287
+ # request parameters.
288
+ #
289
+ # params - Parameters Hash that will be passed to the bulk API call.
290
+ # block - Required block that is used to accumulate bulk API operations.
291
+ # All the operations will be passed to the search cluster via a
292
+ # single API request.
293
+ #
294
+ # Yields a Bulk instance for building bulk API call bodies.
295
+ #
296
+ # Examples
297
+ #
298
+ # docs.bulk do |b|
299
+ # b.index( document1 )
300
+ # b.index( document2 )
301
+ # b.delete( document3 )
302
+ # ...
303
+ # end
304
+ #
305
+ # Returns the response body as a Hash
306
+ def bulk( params = {}, &block )
307
+ raise 'a block is required' if block.nil?
308
+
309
+ params = {:index => self.name, :type => self.type}.merge params
310
+ client.bulk params, &block
311
+ end
312
+
313
+ # Create a new Scan instance for scrolling all results from a `query`.
314
+ # The Scan will be scoped to the current index and document type.
315
+ #
316
+ # query - The query to scan as a Hash or a JSON encoded String
317
+ # opts - Options Hash
318
+ # :index - the name of the index to search
319
+ # :type - the document type to search
320
+ # :scroll - the keep alive time of the scrolling request (5 minutes by default)
321
+ # :size - the number of documents per shard to fetch per scroll
322
+ #
323
+ # Examples
324
+ #
325
+ # scan = docs.scan('{"query":{"match_all":{}}}')
326
+ # scan.each_document do |document|
327
+ # document['_id']
328
+ # document['_source']
329
+ # end
330
+ #
331
+ # Returns a new Scan instance
332
+ def scan( query, opts = {} )
333
+ opts = {:index => name, :type => type}.merge opts
334
+ client.scan query, opts
335
+ end
336
+
337
+ # Execute an array of searches in bulk. Results are returned in an
338
+ # array in the order the queries were sent. The current index name
339
+ # and document type will be passed to the multi_search API call as
340
+ # part of the request parameters.
341
+ #
342
+ # See http://www.elasticsearch.org/guide/reference/api/multi-search/
343
+ #
344
+ # params - Parameters Hash that will be passed to the API call.
345
+ # block - Required block that is used to accumulate searches.
346
+ # All the operations will be passed to the search cluster
347
+ # via a single API request.
348
+ #
349
+ # Yields a MultiSearch instance for building multi_search API call
350
+ # bodies.
351
+ #
352
+ # Examples
353
+ #
354
+ # docs.multi_search do |m|
355
+ # m.search({:query => {:match_all => {}}, :search_type => :count)
356
+ # m.search({:query => {:field => {"foo" => "bar"}}})
357
+ # ...
358
+ # end
359
+ #
360
+ # Returns the response body as a Hash
361
+ def multi_search(params = {}, &block)
362
+ raise 'a block is required' if block.nil?
363
+
364
+ params = {:index => self.name, :type => self.type}.merge params
365
+ client.multi_search params, &block
366
+ end
367
+
368
+ # Internal: Given a `document` generate an options hash that will
369
+ # override parameters based on the content of the document. The document
370
+ # will be returned as the value of the :body key.
371
+ #
372
+ # We only extract information from the document if it is given as a
373
+ # Hash. We do not parse JSON encoded Strings.
374
+ #
375
+ # document - A document Hash or JSON encoded String.
376
+ #
377
+ # Returns an options Hash extracted from the document.
378
+ def from_document( document )
379
+ opts = {:body => document}
380
+
381
+ unless String === document
382
+ %w[_id _type _routing _parent _ttl _timestamp _retry_on_conflict].each do |field|
383
+ key = field.sub(/^_/, '').to_sym
384
+
385
+ opts[key] = document.delete field if document.key? field
386
+ opts[key] = document.delete field.to_sym if document.key? field.to_sym
387
+ end
388
+ end
389
+
390
+ opts
391
+ end
392
+
393
+ # Internal: Add default parameters to the `params` Hash and then apply
394
+ # `overrides` to the params if any are given.
395
+ #
396
+ # params - Parameters Hash
397
+ # overrides - Optional parameter overrides as a Hash
398
+ #
399
+ # Returns a new params Hash.
400
+ def update_params( params, overrides = nil )
401
+ h = defaults.update params
402
+ h.update overrides unless overrides.nil?
403
+ h[:routing] = h[:routing].join(',') if Array === h[:routing]
404
+ h
405
+ end
406
+
407
+ # Internal: Returns a Hash containing default parameters.
408
+ def defaults
409
+ { :index => name, :type => type }
410
+ end
411
+
412
+ # Internal: Allow params to be passed as the first argument to
413
+ # methods that take both an optional query hash and params.
414
+ #
415
+ # query - query hash OR params hash
416
+ # params - params hash OR nil if no query
417
+ #
418
+ # Returns an array of the query (possibly nil) and params Hash.
419
+ def extract_params(query, params=nil)
420
+ if params.nil?
421
+ if query.key? :query
422
+ params = {}
423
+ else
424
+ params, query = query, nil
425
+ end
426
+ end
427
+ [query, params]
428
+ end
429
+
430
+ end # Docs
431
+ end # Client
432
+ end # Elastomer
@@ -0,0 +1,51 @@
1
+
2
+ module Elastomer
3
+
4
+ # Parent class for all Elastomer errors.
5
+ Error = Class.new StandardError
6
+
7
+ class Client
8
+
9
+ # General error response from client requests.
10
+ class Error < ::Elastomer::Error
11
+
12
+ # Construct a new Error from the given response object or a message
13
+ # String. If a response object is given, the error message will be
14
+ # extracted from the response body.
15
+ #
16
+ # response - Faraday::Response object or a simple error message String
17
+ def initialize( response )
18
+ if response.respond_to? :body
19
+ message = Hash === response.body && response.body['error'] || response.body.to_s
20
+ else
21
+ message, response = response.to_s, nil
22
+ end
23
+
24
+ @status = response.nil? ? nil : response.status
25
+
26
+ super message
27
+ end
28
+
29
+ # Returns the status code from the `response` or nil if the Error was not
30
+ # created with a response.
31
+ attr_reader :status
32
+
33
+ end # Error
34
+
35
+ # Timeout specific error class.
36
+ class TimeoutError < ::Elastomer::Error
37
+
38
+ # Wrap a Farday TimeoutError with our own class and include the HTTP
39
+ # path where the error originated.
40
+ #
41
+ # exception - The originating Faraday::Error::TimeoutError
42
+ # path - The path portion of the HTTP request
43
+ #
44
+ def initialize( exception, path )
45
+ super "#{exception.message}: #{path}"
46
+ set_backtrace exception.backtrace
47
+ end
48
+ end # TimeoutError
49
+
50
+ end # Client
51
+ end # Elastomer