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,113 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Client::Cluster do
4
+
5
+ before do
6
+ @name = 'elastomer-cluster-test'
7
+ @index = $client.index @name
8
+ @index.delete if @index.exists?
9
+ @cluster = $client.cluster
10
+ end
11
+
12
+ after do
13
+ @index.delete if @index.exists?
14
+ end
15
+
16
+ it 'gets the cluster health' do
17
+ h = @cluster.health
18
+ assert h.key?('cluster_name'), 'the cluster name is returned'
19
+ assert h.key?('status'), 'the cluster status is returned'
20
+ end
21
+
22
+ it 'gets the cluster state' do
23
+ h = @cluster.state
24
+ assert h.key?('cluster_name'), 'the cluster name is returned'
25
+ assert h.key?('master_node'), 'the master node is returned'
26
+ assert_instance_of Hash, h['nodes'], 'the node list is returned'
27
+ end
28
+
29
+ it 'gets the cluster settings' do
30
+ h = @cluster.settings
31
+ assert_instance_of Hash, h['persistent'], 'the persistent settings are returned'
32
+ assert_instance_of Hash, h['transient'], 'the transient settings are returned'
33
+ end
34
+
35
+ it 'updates the cluster settings' do
36
+ @cluster.update_settings :transient => { 'indices.ttl.interval' => "30" }
37
+ h = @cluster.settings
38
+
39
+ #COMPATIBILITY
40
+ # ES 1.0 changed the default return format of cluster settings to always
41
+ # expand nested properties, e.g.
42
+ # {"indices.ttl.interval": "30"} changed to
43
+ # {"indices": {"ttl": {"interval":"30"}}}
44
+
45
+ # To support both versions, we check for either return format.
46
+ value = h['transient']['indices.ttl.interval'] ||
47
+ h['transient']['indices']['ttl']['interval']
48
+ assert_equal "30", value
49
+
50
+ @cluster.update_settings :transient => { 'indices.ttl.interval' => "60" }
51
+ h = @cluster.settings
52
+
53
+ value = h['transient']['indices.ttl.interval'] ||
54
+ h['transient']['indices']['ttl']['interval']
55
+ assert_equal "60", value
56
+ end
57
+
58
+ it 'returns the list of nodes in the cluster' do
59
+ nodes = @cluster.nodes
60
+ assert !nodes.empty?, 'we have to have some nodes'
61
+ end
62
+
63
+ describe 'when working with aliases' do
64
+ before do
65
+ @name = 'elastomer-cluster-test'
66
+ @index = $client.index @name
67
+ @index.create({}) unless @index.exists?
68
+ wait_for_index(@name)
69
+ end
70
+
71
+ after do
72
+ @index.delete if @index.exists?
73
+ end
74
+
75
+ it 'adds an alias' do
76
+ hash = @cluster.get_aliases
77
+ assert_empty hash[@name]['aliases']
78
+
79
+ @cluster.update_aliases \
80
+ :add => {:index => @name, :alias => 'elastomer-test-unikitty'}
81
+
82
+ hash = @cluster.get_aliases
83
+ assert_equal ['elastomer-test-unikitty'], hash[@name]['aliases'].keys
84
+ end
85
+
86
+ it 'removes an alias' do
87
+ @cluster.update_aliases \
88
+ :add => {:index => @name, :alias => 'elastomer-test-unikitty'}
89
+
90
+ hash = @cluster.get_aliases
91
+ assert_equal ['elastomer-test-unikitty'], hash[@name]['aliases'].keys
92
+
93
+ @cluster.update_aliases([
94
+ {:add => {:index => @name, :alias => 'elastomer-test-SpongeBob-SquarePants'}},
95
+ {:remove => {:index => @name, :alias => 'elastomer-test-unikitty'}}
96
+ ])
97
+
98
+ hash = @cluster.get_aliases
99
+ assert_equal ['elastomer-test-SpongeBob-SquarePants'], hash[@name]['aliases'].keys
100
+ end
101
+
102
+ it 'accepts the full aliases actions hash' do
103
+ @cluster.update_aliases :actions => [
104
+ {:add => {:index => @name, :alias => 'elastomer-test-He-Man'}},
105
+ {:add => {:index => @name, :alias => 'elastomer-test-Skeletor'}}
106
+ ]
107
+
108
+ hash = @cluster.get_aliases(:index => @name)
109
+ assert_equal %w[elastomer-test-He-Man elastomer-test-Skeletor], hash[@name]['aliases'].keys.sort
110
+ end
111
+ end
112
+
113
+ end
@@ -0,0 +1,394 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Client::Docs do
4
+
5
+ before do
6
+ @name = 'elastomer-docs-test'
7
+ @index = $client.index(@name)
8
+
9
+ unless @index.exists?
10
+ @index.create \
11
+ :settings => { 'index.number_of_shards' => 1, 'index.number_of_replicas' => 0 },
12
+ :mappings => {
13
+ :doc1 => {
14
+ :_source => { :enabled => true }, :_all => { :enabled => false },
15
+ :properties => {
16
+ :title => { :type => 'string', :analyzer => 'standard' },
17
+ :author => { :type => 'string', :index => 'not_analyzed' }
18
+ }
19
+ },
20
+ :doc2 => {
21
+ :_source => { :enabled => true }, :_all => { :enabled => false },
22
+ :properties => {
23
+ :title => { :type => 'string', :analyzer => 'standard' },
24
+ :author => { :type => 'string', :index => 'not_analyzed' }
25
+ }
26
+ }
27
+ }
28
+
29
+ wait_for_index(@name)
30
+ end
31
+
32
+ @docs = @index.docs
33
+ end
34
+
35
+ after do
36
+ @index.delete if @index.exists?
37
+ end
38
+
39
+ it 'autogenerates IDs for documents' do
40
+ h = @docs.index \
41
+ :_type => 'doc2',
42
+ :title => 'the author of logging',
43
+ :author => 'pea53'
44
+
45
+ assert_created h
46
+ assert_match %r/^\S{22}$/, h['_id']
47
+
48
+ h = @docs.index \
49
+ :_id => nil,
50
+ :_type => 'doc3',
51
+ :title => 'the author of rubber-band',
52
+ :author => 'grantr'
53
+
54
+ assert_created h
55
+ assert_match %r/^\S{22}$/, h['_id']
56
+
57
+ h = @docs.index \
58
+ :_id => '',
59
+ :_type => 'doc4',
60
+ :title => 'the author of toml',
61
+ :author => 'mojombo'
62
+
63
+ assert_created h
64
+ assert_match %r/^\S{22}$/, h['_id']
65
+ end
66
+
67
+ it 'uses the provided document ID' do
68
+ h = @docs.index \
69
+ :_id => '42',
70
+ :_type => 'doc2',
71
+ :title => 'the author of logging',
72
+ :author => 'pea53'
73
+
74
+ assert_created h
75
+ assert_equal '42', h['_id']
76
+ end
77
+
78
+ it 'accepts JSON encoded document strings' do
79
+ h = @docs.index \
80
+ '{"author":"pea53", "title":"the author of logging"}',
81
+ :id => '42',
82
+ :type => 'doc2'
83
+
84
+ assert_created h
85
+ assert_equal '42', h['_id']
86
+
87
+ h = @docs.index \
88
+ '{"author":"grantr", "title":"the author of rubber-band"}',
89
+ :type => 'doc2'
90
+
91
+ assert_created h
92
+ assert_match %r/^\S{22}$/, h['_id']
93
+ end
94
+
95
+ it 'gets documents from the search index' do
96
+ h = @docs.get :id => '1', :type => 'doc1'
97
+ refute_found h
98
+
99
+ populate!
100
+
101
+ h = @docs.get :id => '1', :type => 'doc1'
102
+ assert_found h
103
+ assert_equal 'mojombo', h['_source']['author']
104
+ end
105
+
106
+ it 'gets multiple documents from the search index' do
107
+ populate!
108
+
109
+ h = @docs.multi_get :docs => [
110
+ { :_id => 1, :_type => 'doc1' },
111
+ { :_id => 1, :_type => 'doc2' }
112
+ ]
113
+ authors = h['docs'].map { |d| d['_source']['author'] }
114
+ assert_equal %w[mojombo pea53], authors
115
+
116
+ h = @docs.multi_get :ids => [2, 1], :_type => 'doc1'
117
+ authors = h['docs'].map { |d| d['_source']['author'] }
118
+ assert_equal %w[defunkt mojombo], authors
119
+
120
+ h = @index.docs('doc1').multi_get :ids => [1, 2, 3, 4]
121
+ assert_found h['docs'][0]
122
+ assert_found h['docs'][1]
123
+ refute_found h['docs'][2]
124
+ refute_found h['docs'][3]
125
+ end
126
+
127
+ it 'deletes documents from the search index' do
128
+ populate!
129
+ @docs = @index.docs('doc2')
130
+
131
+ h = @docs.multi_get :ids => [1, 2]
132
+ authors = h['docs'].map { |d| d['_source']['author'] }
133
+ assert_equal %w[pea53 grantr], authors
134
+
135
+ h = @docs.delete :id => 1
136
+ assert h['found'], "expected document to be found"
137
+ h = @docs.multi_get :ids => [1, 2]
138
+ refute_found h['docs'][0]
139
+ assert_found h['docs'][1]
140
+
141
+ assert_raises(ArgumentError) { @docs.delete :id => nil }
142
+ assert_raises(ArgumentError) { @docs.delete :id => '' }
143
+ assert_raises(ArgumentError) { @docs.delete :id => "\t" }
144
+ end
145
+
146
+ it 'does not care if you delete a document that is not there' do
147
+ @docs = @index.docs('doc2')
148
+ h = @docs.delete :id => 42
149
+
150
+ refute h['found'], 'expected document to not be found'
151
+ end
152
+
153
+ it 'deletes documents by query' do
154
+ populate!
155
+ @docs = @index.docs('doc2')
156
+
157
+ h = @docs.multi_get :ids => [1, 2]
158
+ authors = h['docs'].map { |d| d['_source']['author'] }
159
+ assert_equal %w[pea53 grantr], authors
160
+
161
+ h = @docs.delete_by_query(:q => "author:grantr")
162
+ @index.refresh
163
+ h = @docs.multi_get :ids => [1, 2]
164
+ assert_found h['docs'][0]
165
+ refute_found h['docs'][1]
166
+
167
+ #COMPATIBILITY
168
+ # ES 1.0 normalized all search APIs to use a :query top level element.
169
+ # This broke compatibility with the ES 0.90 delete_by_query api. Since
170
+ # the query hash version of this api never worked with 0.90 in the first
171
+ # place, only test it if running 1.0.
172
+ if es_version_1_x?
173
+ h = @docs.delete_by_query(
174
+ :query => {
175
+ :filtered => {
176
+ :query => {:match_all => {}},
177
+ :filter => {:term => {:author => 'pea53'}}
178
+ }
179
+ }
180
+ )
181
+ @index.refresh
182
+ h = @docs.multi_get :ids => [1, 2]
183
+ refute_found h['docs'][0]
184
+ refute_found h['docs'][1]
185
+ end
186
+ end
187
+
188
+ it 'searches for documents' do
189
+ h = @docs.search :q => '*:*'
190
+ assert_equal 0, h['hits']['total']
191
+
192
+ populate!
193
+
194
+ h = @docs.search :q => '*:*'
195
+ assert_equal 4, h['hits']['total']
196
+
197
+ h = @docs.search :q => '*:*', :type => 'doc1'
198
+ assert_equal 2, h['hits']['total']
199
+
200
+ h = @docs.search({
201
+ :query => {:match_all => {}},
202
+ :filter => {:term => {:author => 'defunkt'}}
203
+ }, :type => %w[doc1 doc2] )
204
+ assert_equal 1, h['hits']['total']
205
+
206
+ hit = h['hits']['hits'].first
207
+ assert_equal 'the author of resque', hit['_source']['title']
208
+ end
209
+
210
+ it 'counts documents' do
211
+ h = @docs.count :q => '*:*'
212
+ assert_equal 0, h['count']
213
+
214
+ populate!
215
+
216
+ h = @docs.count :q => '*:*'
217
+ assert_equal 4, h['count']
218
+
219
+ h = @docs.count :q => '*:*', :type => 'doc1'
220
+ assert_equal 2, h['count']
221
+
222
+ h = @docs.count :q => '*:*', :type => 'doc1,doc2'
223
+ assert_equal 4, h['count']
224
+
225
+ #COMPATIBILITY
226
+ # ES 1.0 normalized all search APIs to use a :query top level element.
227
+ # This broke compatibility with the ES 0.90 count api.
228
+ if es_version_1_x?
229
+ h = @docs.count({
230
+ :query => {
231
+ :filtered => {
232
+ :query => {:match_all => {}},
233
+ :filter => {:term => {:author => 'defunkt'}}
234
+ }
235
+ }
236
+ }, :type => %w[doc1 doc2] )
237
+ else
238
+ h = @docs.count({
239
+ :filtered => {
240
+ :query => {:match_all => {}},
241
+ :filter => {:term => {:author => 'defunkt'}}
242
+ }
243
+ }, :type => %w[doc1 doc2] )
244
+ end
245
+ assert_equal 1, h['count']
246
+ end
247
+
248
+ it 'searches for more like this' do
249
+ populate!
250
+
251
+ # for some reason, if there's no document indexed here all the mlt
252
+ # queries return zero results
253
+ @docs.add \
254
+ :_id => 3,
255
+ :_type => 'doc1',
256
+ :title => 'the author of faraday',
257
+ :author => 'technoweenie'
258
+
259
+ @index.refresh
260
+
261
+ h = @docs.more_like_this({
262
+ :type => 'doc1',
263
+ :id => 1,
264
+ :mlt_fields => 'title',
265
+ :min_term_freq => 1
266
+ })
267
+ assert_equal 2, h["hits"]["total"]
268
+
269
+ h = @docs.more_like_this({
270
+ :facets => {
271
+ "author" => {
272
+ :terms => {
273
+ :field => "author"
274
+ }
275
+ }
276
+ }
277
+ }, {
278
+ :type => 'doc1',
279
+ :id => 1,
280
+ :mlt_fields => 'title,author',
281
+ :min_term_freq => 1
282
+ })
283
+ assert_equal 2, h["hits"]["total"]
284
+ assert_equal 2, h["facets"]["author"]["total"]
285
+ end
286
+
287
+ it 'explains scoring' do
288
+ populate!
289
+
290
+ h = @docs.explain({
291
+ :query => {
292
+ :match => {
293
+ "author" => "defunkt"
294
+ }
295
+ }
296
+ }, :type => 'doc1', :id => 2)
297
+ assert_equal true, h["matched"]
298
+
299
+ h = @docs.explain(:type => 'doc2', :id => 2, :q => "pea53")
300
+ assert_equal false, h["matched"]
301
+ end
302
+
303
+ it 'validates queries' do
304
+ populate!
305
+
306
+ h = @docs.validate :q => '*:*'
307
+ assert_equal true, h["valid"]
308
+
309
+ #COMPATIBILITY
310
+ # ES 1.0 normalized all search APIs to use a :query top level element.
311
+ # This broke compatibility with the ES 0.90 validate api.
312
+ if es_version_1_x?
313
+ h = @docs.validate({
314
+ :query => {
315
+ :filtered => {
316
+ :query => {:match_all => {}},
317
+ :filter => {:term => {:author => 'defunkt'}}
318
+ }
319
+ }
320
+ }, :type => %w[doc1 doc2] )
321
+ else
322
+ h = @docs.validate({
323
+ :filtered => {
324
+ :query => {:match_all => {}},
325
+ :filter => {:term => {:author => 'defunkt'}}
326
+ }
327
+ }, :type => %w[doc1 doc2] )
328
+ end
329
+ assert_equal true, h["valid"]
330
+ end
331
+
332
+ it 'updates documents' do
333
+ populate!
334
+
335
+ h = @docs.get :id => '1', :type => 'doc1'
336
+ assert_found h
337
+ assert_equal 'mojombo', h['_source']['author']
338
+
339
+ @docs.update({
340
+ :_id => '1',
341
+ :_type => 'doc1',
342
+ :doc => {:author => 'TwP'}
343
+ })
344
+ h = @docs.get :id => '1', :type => 'doc1'
345
+ assert_found h
346
+ assert_equal 'TwP', h['_source']['author']
347
+
348
+ if $client.version >= "0.90"
349
+ @docs.update({
350
+ :_id => '42',
351
+ :_type => 'doc1',
352
+ :doc => {
353
+ :author => 'TwP',
354
+ :title => 'the ineffable beauty of search'
355
+ },
356
+ :doc_as_upsert => true
357
+ })
358
+
359
+ h = @docs.get :id => '42', :type => 'doc1'
360
+ assert_found h
361
+ assert_equal 'TwP', h['_source']['author']
362
+ assert_equal 'the ineffable beauty of search', h['_source']['title']
363
+ end
364
+ end
365
+
366
+ def populate!
367
+ @docs.add \
368
+ :_id => 1,
369
+ :_type => 'doc1',
370
+ :title => 'the author of gravatar',
371
+ :author => 'mojombo'
372
+
373
+ @docs.add \
374
+ :_id => 2,
375
+ :_type => 'doc1',
376
+ :title => 'the author of resque',
377
+ :author => 'defunkt'
378
+
379
+ @docs.add \
380
+ :_id => 1,
381
+ :_type => 'doc2',
382
+ :title => 'the author of logging',
383
+ :author => 'pea53'
384
+
385
+ @docs.add \
386
+ :_id => 2,
387
+ :_type => 'doc2',
388
+ :title => 'the author of rubber-band',
389
+ :author => 'grantr'
390
+
391
+ @index.refresh
392
+ end
393
+
394
+ end