elastomer-client 0.3.1
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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +108 -0
- data/Rakefile +9 -0
- data/docs/notifications.md +71 -0
- data/elastomer-client.gemspec +30 -0
- data/lib/elastomer/client.rb +307 -0
- data/lib/elastomer/client/bulk.rb +257 -0
- data/lib/elastomer/client/cluster.rb +208 -0
- data/lib/elastomer/client/docs.rb +432 -0
- data/lib/elastomer/client/errors.rb +51 -0
- data/lib/elastomer/client/index.rb +407 -0
- data/lib/elastomer/client/multi_search.rb +115 -0
- data/lib/elastomer/client/nodes.rb +87 -0
- data/lib/elastomer/client/scan.rb +161 -0
- data/lib/elastomer/client/template.rb +85 -0
- data/lib/elastomer/client/warmer.rb +96 -0
- data/lib/elastomer/core_ext/time.rb +7 -0
- data/lib/elastomer/middleware/encode_json.rb +51 -0
- data/lib/elastomer/middleware/opaque_id.rb +69 -0
- data/lib/elastomer/middleware/parse_json.rb +39 -0
- data/lib/elastomer/notifications.rb +83 -0
- data/lib/elastomer/version.rb +7 -0
- data/script/bootstrap +16 -0
- data/script/cibuild +28 -0
- data/script/console +9 -0
- data/script/testsuite +10 -0
- data/test/assertions.rb +74 -0
- data/test/client/bulk_test.rb +226 -0
- data/test/client/cluster_test.rb +113 -0
- data/test/client/docs_test.rb +394 -0
- data/test/client/index_test.rb +244 -0
- data/test/client/multi_search_test.rb +129 -0
- data/test/client/nodes_test.rb +35 -0
- data/test/client/scan_test.rb +84 -0
- data/test/client/stubbed_client_tests.rb +40 -0
- data/test/client/template_test.rb +33 -0
- data/test/client/warmer_test.rb +56 -0
- data/test/client_test.rb +86 -0
- data/test/core_ext/time_test.rb +46 -0
- data/test/middleware/encode_json_test.rb +53 -0
- data/test/middleware/opaque_id_test.rb +39 -0
- data/test/middleware/parse_json_test.rb +54 -0
- data/test/test_helper.rb +94 -0
- 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
|