sphinx 0.9.9.2117

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 (86) hide show
  1. data/.gitignore +4 -0
  2. data/README.rdoc +243 -0
  3. data/Rakefile +45 -0
  4. data/VERSION.yml +5 -0
  5. data/init.rb +1 -0
  6. data/lib/sphinx/buffered_io.rb +26 -0
  7. data/lib/sphinx/client.rb +2426 -0
  8. data/lib/sphinx/constants.rb +179 -0
  9. data/lib/sphinx/indifferent_access.rb +152 -0
  10. data/lib/sphinx/request.rb +121 -0
  11. data/lib/sphinx/response.rb +71 -0
  12. data/lib/sphinx/server.rb +170 -0
  13. data/lib/sphinx/timeout.rb +31 -0
  14. data/lib/sphinx.rb +51 -0
  15. data/spec/client_response_spec.rb +170 -0
  16. data/spec/client_spec.rb +669 -0
  17. data/spec/client_validations_spec.rb +859 -0
  18. data/spec/fixtures/default_search.php +8 -0
  19. data/spec/fixtures/default_search_index.php +8 -0
  20. data/spec/fixtures/excerpt_custom.php +11 -0
  21. data/spec/fixtures/excerpt_default.php +8 -0
  22. data/spec/fixtures/excerpt_flags.php +12 -0
  23. data/spec/fixtures/field_weights.php +9 -0
  24. data/spec/fixtures/filter.php +9 -0
  25. data/spec/fixtures/filter_exclude.php +9 -0
  26. data/spec/fixtures/filter_float_range.php +9 -0
  27. data/spec/fixtures/filter_float_range_exclude.php +9 -0
  28. data/spec/fixtures/filter_range.php +9 -0
  29. data/spec/fixtures/filter_range_exclude.php +9 -0
  30. data/spec/fixtures/filter_range_int64.php +10 -0
  31. data/spec/fixtures/filter_ranges.php +10 -0
  32. data/spec/fixtures/filters.php +10 -0
  33. data/spec/fixtures/filters_different.php +13 -0
  34. data/spec/fixtures/geo_anchor.php +9 -0
  35. data/spec/fixtures/group_by_attr.php +9 -0
  36. data/spec/fixtures/group_by_attrpair.php +9 -0
  37. data/spec/fixtures/group_by_day.php +9 -0
  38. data/spec/fixtures/group_by_day_sort.php +9 -0
  39. data/spec/fixtures/group_by_month.php +9 -0
  40. data/spec/fixtures/group_by_week.php +9 -0
  41. data/spec/fixtures/group_by_year.php +9 -0
  42. data/spec/fixtures/group_distinct.php +10 -0
  43. data/spec/fixtures/id_range.php +9 -0
  44. data/spec/fixtures/id_range64.php +9 -0
  45. data/spec/fixtures/index_weights.php +9 -0
  46. data/spec/fixtures/keywords.php +8 -0
  47. data/spec/fixtures/limits.php +9 -0
  48. data/spec/fixtures/limits_cutoff.php +9 -0
  49. data/spec/fixtures/limits_max.php +9 -0
  50. data/spec/fixtures/limits_max_cutoff.php +9 -0
  51. data/spec/fixtures/match_all.php +9 -0
  52. data/spec/fixtures/match_any.php +9 -0
  53. data/spec/fixtures/match_boolean.php +9 -0
  54. data/spec/fixtures/match_extended.php +9 -0
  55. data/spec/fixtures/match_extended2.php +9 -0
  56. data/spec/fixtures/match_fullscan.php +9 -0
  57. data/spec/fixtures/match_phrase.php +9 -0
  58. data/spec/fixtures/max_query_time.php +9 -0
  59. data/spec/fixtures/miltiple_queries.php +12 -0
  60. data/spec/fixtures/ranking_bm25.php +9 -0
  61. data/spec/fixtures/ranking_fieldmask.php +9 -0
  62. data/spec/fixtures/ranking_matchany.php +9 -0
  63. data/spec/fixtures/ranking_none.php +9 -0
  64. data/spec/fixtures/ranking_proximity.php +9 -0
  65. data/spec/fixtures/ranking_proximity_bm25.php +9 -0
  66. data/spec/fixtures/ranking_wordcount.php +9 -0
  67. data/spec/fixtures/retries.php +9 -0
  68. data/spec/fixtures/retries_delay.php +9 -0
  69. data/spec/fixtures/select.php +9 -0
  70. data/spec/fixtures/set_override.php +11 -0
  71. data/spec/fixtures/sort_attr_asc.php +9 -0
  72. data/spec/fixtures/sort_attr_desc.php +9 -0
  73. data/spec/fixtures/sort_expr.php +9 -0
  74. data/spec/fixtures/sort_extended.php +9 -0
  75. data/spec/fixtures/sort_relevance.php +9 -0
  76. data/spec/fixtures/sort_time_segments.php +9 -0
  77. data/spec/fixtures/sphinxapi.php +1633 -0
  78. data/spec/fixtures/update_attributes.php +8 -0
  79. data/spec/fixtures/update_attributes_mva.php +8 -0
  80. data/spec/fixtures/weights.php +9 -0
  81. data/spec/spec_helper.rb +24 -0
  82. data/spec/sphinx/sphinx-id64.conf +67 -0
  83. data/spec/sphinx/sphinx.conf +67 -0
  84. data/spec/sphinx/sphinx_test.sql +88 -0
  85. data/sphinx.gemspec +127 -0
  86. metadata +142 -0
@@ -0,0 +1,669 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Sphinx::Client, 'disconnected' do
4
+ context 'in with_server method' do
5
+ before :each do
6
+ @sphinx = Sphinx::Client.new
7
+ @servers = [{:host => 'localhost', :port => 1}, {:host => 'localhost', :port => 2}]
8
+ end
9
+
10
+ context 'without retries' do
11
+ it 'should use single Server instance' do
12
+ 2.times do
13
+ cnt = 0
14
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[0] }
15
+ cnt.should == 1
16
+ end
17
+ end
18
+
19
+ it 'should raise an exception on error' do
20
+ 2.times do
21
+ cnt = 0
22
+ expect {
23
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[0]; raise Sphinx::SphinxConnectError }
24
+ }.to raise_error(Sphinx::SphinxConnectError)
25
+ cnt.should == 1
26
+ end
27
+ end
28
+
29
+ it 'should select server based on index' do
30
+ @sphinx.SetServers(@servers)
31
+ cnt = 0
32
+ @sphinx.send(:with_server, 0) { |server| cnt += 1; server.should == @sphinx.servers[0] }
33
+ cnt.should == 1
34
+ cnt = 0
35
+ @sphinx.send(:with_server, 1) { |server| cnt += 1; server.should == @sphinx.servers[1] }
36
+ cnt.should == 1
37
+ cnt = 0
38
+ @sphinx.send(:with_server, 2) { |server| cnt += 1; server.should == @sphinx.servers[0] }
39
+ cnt.should == 1
40
+ end
41
+
42
+ it 'should select given server' do
43
+ @sphinx.SetServers(@servers)
44
+ cnt = 0
45
+ @sphinx.send(:with_server, @sphinx.servers[0]) { |server| cnt += 1; server.should == @sphinx.servers[0] }
46
+ cnt.should == 1
47
+ cnt = 0
48
+ @sphinx.send(:with_server, @sphinx.servers[1]) { |server| cnt += 1; server.should == @sphinx.servers[1] }
49
+ cnt.should == 1
50
+ end
51
+ end
52
+
53
+ context 'with retries' do
54
+ before :each do
55
+ @sphinx.SetConnectTimeout(0, 3)
56
+ end
57
+
58
+ it 'should raise an exception on error' do
59
+ cnt = 0
60
+ expect {
61
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[0]; raise Sphinx::SphinxConnectError }
62
+ }.to raise_error(Sphinx::SphinxConnectError)
63
+ cnt.should == 3
64
+ end
65
+
66
+ it 'should round-robin servers and raise an exception on error' do
67
+ @sphinx.SetServers(@servers)
68
+ cnt = 0
69
+ expect {
70
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[(cnt - 1) % 2]; raise Sphinx::SphinxConnectError }
71
+ }.to raise_error(Sphinx::SphinxConnectError)
72
+ cnt.should == 3
73
+ end
74
+
75
+ it 'should round-robin servers with respect to passed index and raise an exception on error' do
76
+ @sphinx.SetServers(@servers)
77
+ cnt = 0
78
+ expect {
79
+ @sphinx.send(:with_server, 1) { |server| cnt += 1; server.should == @sphinx.servers[cnt % 2]; raise Sphinx::SphinxConnectError }
80
+ }.to raise_error(Sphinx::SphinxConnectError)
81
+ cnt.should == 3
82
+ end
83
+
84
+ it 'should round-robin with respect to attempts number passed' do
85
+ @sphinx.SetServers(@servers)
86
+ cnt = 0
87
+ expect {
88
+ @sphinx.send(:with_server, 0, 5) { |server| cnt += 1; server.should == @sphinx.servers[(cnt - 1) % 2]; raise Sphinx::SphinxConnectError }
89
+ }.to raise_error(Sphinx::SphinxConnectError)
90
+ cnt.should == 5
91
+ end
92
+ end
93
+ end
94
+
95
+ context 'in with_socket method' do
96
+ before :each do
97
+ @sphinx = Sphinx::Client.new
98
+ @socket = mock('TCPSocket')
99
+ end
100
+
101
+ context 'without retries' do
102
+ before :each do
103
+ @server = mock('Server')
104
+ @server.should_receive(:get_socket).and_yield(@socket).and_return(@socket)
105
+ @server.should_receive(:free_socket).with(@socket).at_least(1)
106
+ end
107
+
108
+ it 'should initialize session' do
109
+ @socket.should_receive(:write).with([1].pack('N'))
110
+ @socket.should_receive(:read).with(4).and_return([1].pack('N'))
111
+ cnt = 0
112
+ @sphinx.send(:with_socket, @server) { |socket| cnt += 1; socket.should == @socket }
113
+ cnt.should == 1
114
+ end
115
+
116
+ it 'should raise exception when searchd protocol is not 1+' do
117
+ @socket.should_receive(:write).with([1].pack('N'))
118
+ @socket.should_receive(:read).with(4).and_return([0].pack('N'))
119
+ cnt = 0
120
+ expect {
121
+ @sphinx.send(:with_socket, @server) { cnt += 1; }
122
+ }.to raise_error(Sphinx::SphinxConnectError, 'expected searchd protocol version 1+, got version \'0\'')
123
+ cnt.should == 0
124
+ end
125
+
126
+ it 'should handle request timeouts' do
127
+ @socket.should_receive(:write).with([1].pack('N'))
128
+ @socket.should_receive(:read).with(4).and_return([1].pack('N'))
129
+ @sphinx.SetRequestTimeout(1)
130
+ cnt = 0
131
+ expect {
132
+ @sphinx.send(:with_socket, @server) { cnt += 1; sleep 2 }
133
+ }.to raise_error(Sphinx::SphinxResponseError, 'failed to read searchd response (msg=time\'s up!)')
134
+ cnt.should == 1
135
+
136
+ @sphinx.GetLastError.should == 'failed to read searchd response (msg=time\'s up!)'
137
+ @sphinx.IsConnectError.should be_false
138
+ end
139
+
140
+ it 'should re-reaise Sphinx errors' do
141
+ @socket.should_receive(:write).with([1].pack('N'))
142
+ @socket.should_receive(:read).with(4).and_return([1].pack('N'))
143
+ cnt = 0
144
+ expect {
145
+ @sphinx.send(:with_socket, @server) { cnt += 1; raise Sphinx::SphinxInternalError, 'hello' }
146
+ }.to raise_error(Sphinx::SphinxInternalError, 'hello')
147
+ cnt.should == 1
148
+
149
+ @sphinx.GetLastError.should == 'hello'
150
+ @sphinx.IsConnectError.should be_false
151
+ end
152
+ end
153
+
154
+ context 'with retries' do
155
+ before :each do
156
+ @sphinx.SetRequestTimeout(0, 3)
157
+ # two more times yielding - retries
158
+ @server = mock('Server')
159
+ @server.should_receive(:get_socket).at_least(1).times.and_yield(@socket).and_return(@socket)
160
+ @server.should_receive(:free_socket).with(@socket).at_least(1)
161
+ end
162
+
163
+ it 'should raise an exception on error' do
164
+ @socket.should_receive(:write).exactly(3).times.with([1].pack('N'))
165
+ @socket.should_receive(:read).exactly(3).times.with(4).and_return([1].pack('N'))
166
+ cnt = 0
167
+ expect {
168
+ @sphinx.send(:with_socket, @server) { cnt += 1; raise Sphinx::SphinxInternalError, 'hello' }
169
+ }.to raise_error(Sphinx::SphinxInternalError, 'hello')
170
+ cnt.should == 3
171
+
172
+ @sphinx.GetLastError.should == 'hello'
173
+ @sphinx.IsConnectError.should be_false
174
+ end
175
+ end
176
+ end
177
+
178
+ context 'in parse_response method' do
179
+ before :each do
180
+ @sphinx = Sphinx::Client.new
181
+ @socket = mock('TCPSocket')
182
+ end
183
+
184
+ it 'should receive response' do
185
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_OK, 1, 4].pack('n2N'))
186
+ @socket.should_receive(:read).with(4).and_return([0].pack('N'))
187
+ @sphinx.send(:parse_response, @socket, 1)
188
+ end
189
+
190
+ it 'should raise exception on zero-sized response' do
191
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_OK, 1, 0].pack('n2N'))
192
+ expect {
193
+ @sphinx.send(:parse_response, @socket, 1)
194
+ }.to raise_error(Sphinx::SphinxResponseError, 'received zero-sized searchd response')
195
+ end
196
+
197
+ it 'should raise exception when response is incomplete' do
198
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_OK, 1, 4].pack('n2N'))
199
+ @socket.should_receive(:read).with(4).and_return('')
200
+ expect {
201
+ @sphinx.send(:parse_response, @socket, 1)
202
+ }.to raise_error(Sphinx::SphinxResponseError)
203
+ end
204
+
205
+ it 'should set warning message when SEARCHD_WARNING received' do
206
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_WARNING, 1, 14].pack('n2N'))
207
+ @socket.should_receive(:read).with(14).and_return([5].pack('N') + 'helloworld')
208
+ @sphinx.send(:parse_response, @socket, 1).should == 'world'
209
+ @sphinx.GetLastWarning.should == 'hello'
210
+ end
211
+
212
+ it 'should raise exception when SEARCHD_ERROR received' do
213
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_ERROR, 1, 9].pack('n2N'))
214
+ @socket.should_receive(:read).with(9).and_return([1].pack('N') + 'hello')
215
+ expect {
216
+ @sphinx.send(:parse_response, @socket, 1)
217
+ }.to raise_error(Sphinx::SphinxInternalError, 'searchd error: hello')
218
+ end
219
+
220
+ it 'should raise exception when SEARCHD_RETRY received' do
221
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_RETRY, 1, 9].pack('n2N'))
222
+ @socket.should_receive(:read).with(9).and_return([1].pack('N') + 'hello')
223
+ expect {
224
+ @sphinx.send(:parse_response, @socket, 1)
225
+ }.to raise_error(Sphinx::SphinxTemporaryError, 'temporary searchd error: hello')
226
+ end
227
+
228
+ it 'should raise exception when unknown status received' do
229
+ @socket.should_receive(:read).with(8).and_return([65535, 1, 9].pack('n2N'))
230
+ @socket.should_receive(:read).with(9).and_return([1].pack('N') + 'hello')
231
+ expect {
232
+ @sphinx.send(:parse_response, @socket, 1)
233
+ }.to raise_error(Sphinx::SphinxUnknownError, 'unknown status code: \'65535\'')
234
+ end
235
+
236
+ it 'should set warning when server is older than client' do
237
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_OK, 1, 9].pack('n2N'))
238
+ @socket.should_receive(:read).with(9).and_return([1].pack('N') + 'hello')
239
+ @sphinx.send(:parse_response, @socket, 5)
240
+ @sphinx.GetLastWarning.should == 'searchd command v.0.1 older than client\'s v.0.5, some options might not work'
241
+ end
242
+ end
243
+
244
+ context 'in Query method' do
245
+ before :each do
246
+ @sphinx = sphinx_create_client
247
+ end
248
+
249
+ it 'should generate valid request with default parameters' do
250
+ expected = sphinx_fixture('default_search')
251
+ @sock.should_receive(:write).with(expected)
252
+ sphinx_safe_call { @sphinx.Query('query') }
253
+ end
254
+
255
+ it 'should generate valid request with default parameters and index' do
256
+ expected = sphinx_fixture('default_search_index')
257
+ @sock.should_receive(:write).with(expected)
258
+ sphinx_safe_call { @sphinx.Query('query', 'index') }
259
+ end
260
+
261
+ it 'should generate valid request with limits' do
262
+ expected = sphinx_fixture('limits')
263
+ @sock.should_receive(:write).with(expected)
264
+ @sphinx.SetLimits(10, 20)
265
+ sphinx_safe_call { @sphinx.Query('query') }
266
+ end
267
+
268
+ it 'should generate valid request with limits and max number to retrieve' do
269
+ expected = sphinx_fixture('limits_max')
270
+ @sock.should_receive(:write).with(expected)
271
+ @sphinx.SetLimits(10, 20, 30)
272
+ sphinx_safe_call { @sphinx.Query('query') }
273
+ end
274
+
275
+ it 'should generate valid request with limits and cutoff to retrieve' do
276
+ expected = sphinx_fixture('limits_cutoff')
277
+ @sock.should_receive(:write).with(expected)
278
+ @sphinx.SetLimits(10, 20, 30, 40)
279
+ sphinx_safe_call { @sphinx.Query('query') }
280
+ end
281
+
282
+ it 'should generate valid request with max query time specified' do
283
+ expected = sphinx_fixture('max_query_time')
284
+ @sock.should_receive(:write).with(expected)
285
+ @sphinx.SetMaxQueryTime(1000)
286
+ sphinx_safe_call { @sphinx.Query('query') }
287
+ end
288
+
289
+ describe 'with match' do
290
+ [ :all, :any, :phrase, :boolean, :extended, :fullscan, :extended2 ].each do |match|
291
+ it "should generate valid request for SPH_MATCH_#{match.to_s.upcase}" do
292
+ expected = sphinx_fixture("match_#{match}")
293
+ @sock.should_receive(:write).with(expected)
294
+ @sphinx.SetMatchMode(Sphinx::const_get("SPH_MATCH_#{match.to_s.upcase}"))
295
+ sphinx_safe_call { @sphinx.Query('query') }
296
+ end
297
+
298
+ it "should generate valid request for \"#{match}\"" do
299
+ expected = sphinx_fixture("match_#{match}")
300
+ @sock.should_receive(:write).with(expected)
301
+ @sphinx.SetMatchMode(match.to_s)
302
+ sphinx_safe_call { @sphinx.Query('query') }
303
+ end
304
+
305
+ it "should generate valid request for :#{match}" do
306
+ expected = sphinx_fixture("match_#{match}")
307
+ @sock.should_receive(:write).with(expected)
308
+ @sphinx.SetMatchMode(match)
309
+ sphinx_safe_call { @sphinx.Query('query') }
310
+ end
311
+ end
312
+ end
313
+
314
+ describe 'with rank' do
315
+ [ :proximity_bm25, :bm25, :none, :wordcount, :proximity, :matchany, :fieldmask ].each do |rank|
316
+ it "should generate valid request for SPH_RANK_#{rank.to_s.upcase}" do
317
+ expected = sphinx_fixture("ranking_#{rank}")
318
+ @sock.should_receive(:write).with(expected)
319
+ @sphinx.SetRankingMode(Sphinx::Client.const_get("SPH_RANK_#{rank.to_s.upcase}"))
320
+ sphinx_safe_call { @sphinx.Query('query') }
321
+ end
322
+
323
+ it "should generate valid request for \"#{rank}\"" do
324
+ expected = sphinx_fixture("ranking_#{rank}")
325
+ @sock.should_receive(:write).with(expected)
326
+ @sphinx.SetRankingMode(rank.to_s)
327
+ sphinx_safe_call { @sphinx.Query('query') }
328
+ end
329
+
330
+ it "should generate valid request for :#{rank}" do
331
+ expected = sphinx_fixture("ranking_#{rank}")
332
+ @sock.should_receive(:write).with(expected)
333
+ @sphinx.SetRankingMode(rank)
334
+ sphinx_safe_call { @sphinx.Query('query') }
335
+ end
336
+ end
337
+ end
338
+
339
+ describe 'with sorting' do
340
+ [ :attr_desc, :relevance, :attr_asc, :time_segments, :extended, :expr ].each do |mode|
341
+ it "should generate valid request for SPH_SORT_#{mode.to_s.upcase}" do
342
+ expected = sphinx_fixture("sort_#{mode}")
343
+ @sock.should_receive(:write).with(expected)
344
+ @sphinx.SetSortMode(Sphinx::Client.const_get("SPH_SORT_#{mode.to_s.upcase}"), mode == :relevance ? '' : 'sortby')
345
+ sphinx_safe_call { @sphinx.Query('query') }
346
+ end
347
+
348
+ it "should generate valid request for \"#{mode}\"" do
349
+ expected = sphinx_fixture("sort_#{mode}")
350
+ @sock.should_receive(:write).with(expected)
351
+ @sphinx.SetSortMode(mode.to_s, mode == :relevance ? '' : 'sortby')
352
+ sphinx_safe_call { @sphinx.Query('query') }
353
+ end
354
+
355
+ it "should generate valid request for :#{mode}" do
356
+ expected = sphinx_fixture("sort_#{mode}")
357
+ @sock.should_receive(:write).with(expected)
358
+ @sphinx.SetSortMode(mode, mode == :relevance ? '' : 'sortby')
359
+ sphinx_safe_call { @sphinx.Query('query') }
360
+ end
361
+ end
362
+ end
363
+
364
+ it 'should generate valid request with weights' do
365
+ expected = sphinx_fixture('weights')
366
+ @sock.should_receive(:write).with(expected)
367
+ @sphinx.SetWeights([10, 20, 30, 40])
368
+ sphinx_safe_call { @sphinx.Query('query') }
369
+ end
370
+
371
+ it 'should generate valid request with field weights' do
372
+ expected = sphinx_fixture('field_weights')
373
+ @sock.should_receive(:write).with(expected)
374
+ @sphinx.SetFieldWeights({'field1' => 10, 'field2' => 20})
375
+ sphinx_safe_call { @sphinx.Query('query') }
376
+ end
377
+
378
+ it 'should generate valid request with index weights' do
379
+ expected = sphinx_fixture('index_weights')
380
+ @sock.should_receive(:write).with(expected)
381
+ @sphinx.SetIndexWeights({'index1' => 10, 'index2' => 20})
382
+ sphinx_safe_call { @sphinx.Query('query') }
383
+ end
384
+
385
+ it 'should generate valid request with ID range' do
386
+ expected = sphinx_fixture('id_range')
387
+ @sock.should_receive(:write).with(expected)
388
+ @sphinx.SetIDRange(10, 20)
389
+ sphinx_safe_call { @sphinx.Query('query') }
390
+ end
391
+
392
+ it 'should generate valid request with ID range and 64-bit ints' do
393
+ expected = sphinx_fixture('id_range64')
394
+ @sock.should_receive(:write).with(expected)
395
+ @sphinx.SetIDRange(8589934591, 17179869183)
396
+ sphinx_safe_call { @sphinx.Query('query') }
397
+ end
398
+
399
+ it 'should generate valid request with values filter' do
400
+ expected = sphinx_fixture('filter')
401
+ @sock.should_receive(:write).with(expected)
402
+ @sphinx.SetFilter('attr', [10, 20, 30])
403
+ sphinx_safe_call { @sphinx.Query('query') }
404
+ end
405
+
406
+ it 'should generate valid request with two values filters' do
407
+ expected = sphinx_fixture('filters')
408
+ @sock.should_receive(:write).with(expected)
409
+ @sphinx.SetFilter('attr2', [40, 50])
410
+ @sphinx.SetFilter('attr1', [10, 20, 30])
411
+ sphinx_safe_call { @sphinx.Query('query') }
412
+ end
413
+
414
+ it 'should generate valid request with values filter excluded' do
415
+ expected = sphinx_fixture('filter_exclude')
416
+ @sock.should_receive(:write).with(expected)
417
+ @sphinx.SetFilter('attr', [10, 20, 30], true)
418
+ sphinx_safe_call { @sphinx.Query('query') }
419
+ end
420
+
421
+ it 'should generate valid request with values filter range' do
422
+ expected = sphinx_fixture('filter_range')
423
+ @sock.should_receive(:write).with(expected)
424
+ @sphinx.SetFilterRange('attr', 10, 20)
425
+ sphinx_safe_call { @sphinx.Query('query') }
426
+ end
427
+
428
+ it 'should generate valid request with two filter ranges' do
429
+ expected = sphinx_fixture('filter_ranges')
430
+ @sock.should_receive(:write).with(expected)
431
+ @sphinx.SetFilterRange('attr2', 30, 40)
432
+ @sphinx.SetFilterRange('attr1', 10, 20)
433
+ sphinx_safe_call { @sphinx.Query('query') }
434
+ end
435
+
436
+ it 'should generate valid request with filter range excluded' do
437
+ expected = sphinx_fixture('filter_range_exclude')
438
+ @sock.should_receive(:write).with(expected)
439
+ @sphinx.SetFilterRange('attr', 10, 20, true)
440
+ sphinx_safe_call { @sphinx.Query('query') }
441
+ end
442
+
443
+ it 'should generate valid request with signed int64-based filter range' do
444
+ expected = sphinx_fixture('filter_range_int64')
445
+ @sock.should_receive(:write).with(expected)
446
+ @sphinx.SetFilterRange('attr1', -10, 20)
447
+ @sphinx.SetFilterRange('attr2', -1099511627770, 1099511627780)
448
+ sphinx_safe_call { @sphinx.Query('query') }
449
+ end
450
+
451
+ it 'should generate valid request with float filter range' do
452
+ expected = sphinx_fixture('filter_float_range')
453
+ @sock.should_receive(:write).with(expected)
454
+ @sphinx.SetFilterFloatRange('attr', 10.5, 20.3)
455
+ sphinx_safe_call { @sphinx.Query('query') }
456
+ end
457
+
458
+ it 'should generate valid request with float filter excluded' do
459
+ expected = sphinx_fixture('filter_float_range_exclude')
460
+ @sock.should_receive(:write).with(expected)
461
+ @sphinx.SetFilterFloatRange('attr', 10.5, 20.3, true)
462
+ sphinx_safe_call { @sphinx.Query('query') }
463
+ end
464
+
465
+ it 'should generate valid request with different filters' do
466
+ expected = sphinx_fixture('filters_different')
467
+ @sock.should_receive(:write).with(expected)
468
+ @sphinx.SetFilterRange('attr1', 10, 20, true)
469
+ @sphinx.SetFilter('attr3', [30, 40, 50])
470
+ @sphinx.SetFilterRange('attr1', 60, 70)
471
+ @sphinx.SetFilter('attr2', [80, 90, 100], true)
472
+ @sphinx.SetFilterFloatRange('attr1', 60.8, 70.5)
473
+ sphinx_safe_call { @sphinx.Query('query') }
474
+ end
475
+
476
+ it 'should generate valid request with geographical anchor point' do
477
+ expected = sphinx_fixture('geo_anchor')
478
+ @sock.should_receive(:write).with(expected)
479
+ @sphinx.SetGeoAnchor('attrlat', 'attrlong', 20.3, 40.7)
480
+ sphinx_safe_call { @sphinx.Query('query') }
481
+ end
482
+
483
+ describe 'with group by' do
484
+ [ :day, :week, :month, :year, :attr, :attrpair ].each do |groupby|
485
+ it "should generate valid request for SPH_GROUPBY_#{groupby.to_s.upcase}" do
486
+ expected = sphinx_fixture("group_by_#{groupby}")
487
+ @sock.should_receive(:write).with(expected)
488
+ @sphinx.SetGroupBy('attr', Sphinx::const_get("SPH_GROUPBY_#{groupby.to_s.upcase}"))
489
+ sphinx_safe_call { @sphinx.Query('query') }
490
+ end
491
+
492
+ it "should generate valid request for \"#{groupby}\"" do
493
+ expected = sphinx_fixture("group_by_#{groupby}")
494
+ @sock.should_receive(:write).with(expected)
495
+ @sphinx.SetGroupBy('attr', groupby.to_s)
496
+ sphinx_safe_call { @sphinx.Query('query') }
497
+ end
498
+
499
+ it "should generate valid request for :#{groupby}" do
500
+ expected = sphinx_fixture("group_by_#{groupby}")
501
+ @sock.should_receive(:write).with(expected)
502
+ @sphinx.SetGroupBy('attr', groupby)
503
+ sphinx_safe_call { @sphinx.Query('query') }
504
+ end
505
+ end
506
+
507
+ it 'should generate valid request for SPH_GROUPBY_DAY with sort' do
508
+ expected = sphinx_fixture('group_by_day_sort')
509
+ @sock.should_receive(:write).with(expected)
510
+ @sphinx.SetGroupBy('attr', Sphinx::SPH_GROUPBY_DAY, 'somesort')
511
+ sphinx_safe_call { @sphinx.Query('query') }
512
+ end
513
+
514
+ it 'should generate valid request with count-distinct attribute' do
515
+ expected = sphinx_fixture('group_distinct')
516
+ @sock.should_receive(:write).with(expected)
517
+ @sphinx.SetGroupBy('attr', Sphinx::SPH_GROUPBY_DAY)
518
+ @sphinx.SetGroupDistinct('attr')
519
+ sphinx_safe_call { @sphinx.Query('query') }
520
+ end
521
+ end
522
+
523
+ it 'should generate valid request with retries count specified' do
524
+ expected = sphinx_fixture('retries')
525
+ @sock.should_receive(:write).with(expected)
526
+ @sphinx.SetRetries(10)
527
+ sphinx_safe_call { @sphinx.Query('query') }
528
+ end
529
+
530
+ it 'should generate valid request with retries count and delay specified' do
531
+ expected = sphinx_fixture('retries_delay')
532
+ @sock.should_receive(:write).with(expected)
533
+ @sphinx.SetRetries(10, 20)
534
+ sphinx_safe_call { @sphinx.Query('query') }
535
+ end
536
+
537
+ it 'should generate valid request for SetOverride' do
538
+ expected = sphinx_fixture('set_override')
539
+ @sock.should_receive(:write).with(expected)
540
+ @sphinx.SetOverride('attr1', Sphinx::SPH_ATTR_INTEGER, { 10 => 20 })
541
+ @sphinx.SetOverride('attr2', Sphinx::SPH_ATTR_FLOAT, { 11 => 30.3 })
542
+ @sphinx.SetOverride('attr3', Sphinx::SPH_ATTR_BIGINT, { 12 => 1099511627780 })
543
+ sphinx_safe_call { @sphinx.Query('query') }
544
+ end
545
+
546
+ it 'should generate valid request for SetSelect' do
547
+ expected = sphinx_fixture('select')
548
+ @sock.should_receive(:write).with(expected)
549
+ @sphinx.SetSelect('attr1, attr2')
550
+ sphinx_safe_call { @sphinx.Query('query') }
551
+ end
552
+ end
553
+
554
+ context 'in RunQueries method' do
555
+ before(:each) do
556
+ @sphinx = sphinx_create_client
557
+ end
558
+
559
+ it 'should generate valid request for multiple queries' do
560
+ expected = sphinx_fixture('miltiple_queries')
561
+ @sock.should_receive(:write).with(expected)
562
+
563
+ @sphinx.SetRetries(10, 20)
564
+ @sphinx.AddQuery('test1')
565
+ @sphinx.SetGroupBy('attr', Sphinx::SPH_GROUPBY_DAY)
566
+ @sphinx.AddQuery('test2')
567
+
568
+ sphinx_safe_call { @sphinx.RunQueries }
569
+ end
570
+ end
571
+
572
+ context 'in BuildExcerpts method' do
573
+ before :each do
574
+ @sphinx = sphinx_create_client
575
+ end
576
+
577
+ it 'should generate valid request with default parameters' do
578
+ expected = sphinx_fixture('excerpt_default')
579
+ @sock.should_receive(:write).with(expected)
580
+ sphinx_safe_call { @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2') }
581
+ end
582
+
583
+ it 'should generate valid request with custom parameters' do
584
+ expected = sphinx_fixture('excerpt_custom')
585
+ @sock.should_receive(:write).with(expected)
586
+ sphinx_safe_call do
587
+ @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2', { 'before_match' => 'before',
588
+ 'after_match' => 'after',
589
+ 'chunk_separator' => 'separator',
590
+ 'limit' => 10 })
591
+ end
592
+ end
593
+
594
+ it 'should generate valid request with custom parameters as symbols' do
595
+ expected = sphinx_fixture('excerpt_custom')
596
+ @sock.should_receive(:write).with(expected)
597
+ sphinx_safe_call do
598
+ @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2', { :before_match => 'before',
599
+ :after_match => 'after',
600
+ :chunk_separator => 'separator',
601
+ :limit => 10 })
602
+ end
603
+ end
604
+
605
+ it 'should generate valid request with flags' do
606
+ expected = sphinx_fixture('excerpt_flags')
607
+ @sock.should_receive(:write).with(expected)
608
+ sphinx_safe_call do
609
+ @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2', { 'exact_phrase' => true,
610
+ 'single_passage' => true,
611
+ 'use_boundaries' => true,
612
+ 'weight_order' => true,
613
+ 'query_mode' => true })
614
+ end
615
+ end
616
+
617
+ it 'should generate valid request with flags as symbols' do
618
+ expected = sphinx_fixture('excerpt_flags')
619
+ @sock.should_receive(:write).with(expected)
620
+ sphinx_safe_call do
621
+ @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2', { :exact_phrase => true,
622
+ :single_passage => true,
623
+ :use_boundaries => true,
624
+ :weight_order => true,
625
+ :query_mode => true })
626
+ end
627
+ end
628
+ end
629
+
630
+ context 'in BuildKeywords method' do
631
+ before :each do
632
+ @sphinx = sphinx_create_client
633
+ end
634
+
635
+ it 'should generate valid request' do
636
+ expected = sphinx_fixture('keywords')
637
+ @sock.should_receive(:write).with(expected)
638
+ sphinx_safe_call { @sphinx.BuildKeywords('test', 'index', true) }
639
+ end
640
+ end
641
+
642
+ context 'in UpdateAttributes method' do
643
+ before :each do
644
+ @sphinx = sphinx_create_client
645
+ end
646
+
647
+ it 'should generate valid request' do
648
+ expected = sphinx_fixture('update_attributes')
649
+ @sock.should_receive(:write).with(expected)
650
+ sphinx_safe_call { @sphinx.UpdateAttributes('index', ['group'], { 123 => [456] }) }
651
+ end
652
+
653
+ it 'should generate valid request for MVA' do
654
+ expected = sphinx_fixture('update_attributes_mva')
655
+ @sock.should_receive(:write).with(expected)
656
+ sphinx_safe_call { @sphinx.UpdateAttributes('index', ['group', 'category'], { 123 => [ [456, 789], [1, 2, 3] ] }, true) }
657
+ end
658
+ end
659
+
660
+ context 'in EscapeString method' do
661
+ before :each do
662
+ @sphinx = Sphinx::Client.new
663
+ end
664
+
665
+ it 'should escape special characters' do
666
+ @sphinx.escape_string("escaping-sample@query/string").should == "escaping\\-sample\\@query\\/string"
667
+ end
668
+ end
669
+ end