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