sphinx 0.9.10 → 0.9.10.2043

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,167 @@
1
+ class Sphinx::Server
2
+ # The host the Sphinx server is running on
3
+ attr_reader :host
4
+
5
+ # The port the Sphinx server is listening on
6
+ attr_reader :port
7
+
8
+ # The path to UNIX socket where Sphinx server is running on
9
+ attr_reader :path
10
+
11
+ # Creates a new instance of +Server+.
12
+ #
13
+ # Parameters:
14
+ # * +sphinx+ -- an instance of <tt>Sphinx::Client</tt>.
15
+ # * +host+ -- name of host where search is running (if +path+ is not specified).
16
+ # * +port+ -- searchd port (if +path+ is not specified).
17
+ # * +path+ -- an absolute path to the UNIX socket.
18
+ #
19
+ def initialize(sphinx, host, port, path)
20
+ @sphinx = sphinx
21
+ @host = host
22
+ @port = port
23
+ @path = path
24
+
25
+ @timeout = @sphinx.timeout
26
+ @retries = @sphinx.retries
27
+
28
+ @reqtimeout = @sphinx.reqtimeout
29
+ @reqretries = @sphinx.reqretries
30
+
31
+ @socket = nil
32
+ end
33
+
34
+ # Gets the opened socket to the server.
35
+ #
36
+ # You can pass a block to make any connection establishing related things,
37
+ # like protocol version interchange. They will be treated as a part of
38
+ # connection routine, so connection timeout will include them.
39
+ #
40
+ # In case of connection error, +SphinxConnectError+ exception will be raised.
41
+ #
42
+ # Method returns opened socket, so do not forget to close it using +free_socket+
43
+ # method. Make sure you will close socket in case of any emergency.
44
+ #
45
+ def get_socket(&block)
46
+ if persistent?
47
+ yield @socket
48
+ @socket
49
+ else
50
+ socket = nil
51
+ Sphinx::safe_execute(@timeout) do
52
+ socket = establish_connection
53
+
54
+ # Do custom initialization
55
+ yield socket if block_given?
56
+ end
57
+ socket
58
+ end
59
+ rescue SocketError, SystemCallError, IOError, EOFError, ::Timeout::Error, ::Errno::EPIPE => e
60
+ # Close previously opened socket (in case of it has been really opened)
61
+ free_socket(socket, true)
62
+
63
+ location = @path || "#{@host}:#{@port}"
64
+ error = "connection to #{location} failed ("
65
+ if e.kind_of?(SystemCallError)
66
+ error << "errno=#{e.class::Errno}, "
67
+ end
68
+ error << "msg=#{e.message})"
69
+ raise SphinxConnectError, error
70
+ end
71
+
72
+ # Closes previously opened socket.
73
+ #
74
+ # Pass socket retrieved with +get_socket+ method when finished work. It does
75
+ # not close persistent sockets, but if really you need to do it, pass +true+
76
+ # as +force+ parameter value.
77
+ #
78
+ def free_socket(socket, force = false)
79
+ # Socket has not been open
80
+ return false if socket.nil?
81
+
82
+ # Do we try to close persistent socket?
83
+ if socket == @socket
84
+ # do not close it if not forced
85
+ if force
86
+ @socket.close unless @socket.closed?
87
+ @socket = nil
88
+ true
89
+ else
90
+ false
91
+ end
92
+ else
93
+ # Just close this socket
94
+ socket.close unless socket.closed?
95
+ true
96
+ end
97
+ end
98
+
99
+ # Makes specified socket persistent.
100
+ #
101
+ # Previous persistent socket will be closed as well.
102
+ def make_persistent!(socket)
103
+ free_socket(@socket)
104
+ @socket = socket
105
+ end
106
+
107
+ # Closes persistent socket.
108
+ def close_persistent!
109
+ if @socket
110
+ @socket.close unless @socket.closed?
111
+ @socket = nil
112
+ true
113
+ else
114
+ false
115
+ end
116
+ end
117
+
118
+ # Gets a value indicating whether server has persistent socket associated.
119
+ def persistent?
120
+ !@socket.nil?
121
+ end
122
+
123
+ private
124
+
125
+ # This is internal method which establishes a connection to a configured server.
126
+ #
127
+ # Method configures various socket options (like TCP_NODELAY), and
128
+ # sets socket timeouts.
129
+ #
130
+ # It does not close socket on any failure, please do it from calling code!
131
+ #
132
+ def establish_connection
133
+ if @path
134
+ sock = UNIXSocket.new(@path)
135
+ else
136
+ sock = TCPSocket.new(@host, @port)
137
+ end
138
+
139
+ io = Sphinx::BufferedIO.new(sock)
140
+ io.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
141
+ if @reqtimeout > 0
142
+ io.read_timeout = @reqtimeout
143
+
144
+ # This is a part of memcache-client library.
145
+ #
146
+ # Getting reports from several customers, including 37signals,
147
+ # that the non-blocking timeouts in 1.7.5 don't seem to be reliable.
148
+ # It can't hurt to set the underlying socket timeout also, if possible.
149
+ if timeout
150
+ secs = Integer(timeout)
151
+ usecs = Integer((timeout - secs) * 1_000_000)
152
+ optval = [secs, usecs].pack("l_2")
153
+ begin
154
+ io.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
155
+ io.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
156
+ rescue Exception => ex
157
+ # Solaris, for one, does not like/support socket timeouts.
158
+ @warning = "Unable to use raw socket timeouts: #{ex.class.name}: #{ex.message}"
159
+ end
160
+ end
161
+ else
162
+ io.read_timeout = false
163
+ end
164
+
165
+ io
166
+ end
167
+ end
@@ -0,0 +1,28 @@
1
+ module Sphinx
2
+ begin
3
+ # Try to use the SystemTimer gem instead of Ruby's timeout library
4
+ # when running on something that looks like Ruby 1.8.x. See:
5
+ # http://ph7spot.com/articles/system_timer
6
+ # We don't want to bother trying to load SystemTimer on jruby and
7
+ # ruby 1.9+
8
+ if defined?(JRUBY_VERSION) || (RUBY_VERSION >= '1.9')
9
+ require 'timeout'
10
+ Timeout = ::Timeout
11
+ else
12
+ require 'system_timer'
13
+ Timeout = ::SystemTimer
14
+ end
15
+ rescue LoadError => e
16
+ puts "[sphinx] Could not load SystemTimer gem, falling back to Ruby's slower/unsafe timeout library: #{e.message}"
17
+ require 'timeout'
18
+ Timeout = ::Timeout
19
+ end
20
+
21
+ def self.safe_execute(timeout = 5, &block)
22
+ if timeout > 0
23
+ Sphinx::Timeout.timeout(timeout, &block)
24
+ else
25
+ yield
26
+ end
27
+ end
28
+ end
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../init'
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
2
 
3
3
  # To execute these tests you need to execute sphinx_test.sql and configure sphinx using sphinx.conf
4
4
  # (both files are placed under sphinx directory)
@@ -24,7 +24,7 @@ describe Sphinx::Client, 'connected' do
24
24
 
25
25
  it 'should process errors in Query method' do
26
26
  @sphinx.Query('wifi', 'fakeindex').should be_false
27
- @sphinx.GetLastError.length.should_not == 0
27
+ @sphinx.GetLastError.should_not be_empty
28
28
  end
29
29
  end
30
30
 
@@ -34,14 +34,14 @@ describe Sphinx::Client, 'connected' do
34
34
  @sphinx.AddQuery('gprs', 'test1')
35
35
  results = @sphinx.RunQueries
36
36
  results.should be_an_instance_of(Array)
37
- results.length.should == 2
37
+ results.should have(2).items
38
38
  validate_results_wifi(results[0])
39
39
  end
40
40
 
41
41
  it 'should process errors in RunQueries method' do
42
42
  @sphinx.AddQuery('wifi', 'fakeindex')
43
43
  r = @sphinx.RunQueries
44
- r[0]['error'].length.should_not == 0
44
+ r[0]['error'].should_not be_empty
45
45
  end
46
46
  end
47
47
 
@@ -62,25 +62,6 @@ describe Sphinx::Client, 'connected' do
62
62
  end
63
63
  end
64
64
 
65
- context 'in Open method' do
66
- it 'should open socket' do
67
- @sphinx.Open.should be_true
68
- socket = @sphinx.instance_variable_get(:@socket)
69
- socket.should be_kind_of(TCPSocket)
70
- socket.close
71
- end
72
-
73
- it 'should produce an error when opened twice' do
74
- @sphinx.Open.should be_true
75
- @sphinx.Open.should be_false
76
- @sphinx.GetLastError.should == 'already connected'
77
-
78
- socket = @sphinx.instance_variable_get(:@socket)
79
- socket.should be_kind_of(TCPSocket)
80
- socket.close
81
- end
82
- end
83
-
84
65
  context 'in UpdateAttributes method' do
85
66
  it 'should parse response' do
86
67
  @sphinx.UpdateAttributes('test1', ['group_id'], { 2 => [1] }).should == 1
@@ -101,23 +82,43 @@ describe Sphinx::Client, 'connected' do
101
82
  end
102
83
  end
103
84
 
85
+ context 'in Open method' do
86
+ it 'should open socket' do
87
+ @sphinx.Open.should be_true
88
+ socket = @sphinx.servers.first.instance_variable_get(:@socket)
89
+ socket.should_not be_nil
90
+ socket.should be_kind_of(Sphinx::BufferedIO)
91
+ socket.close
92
+ end
93
+
94
+ it 'should produce an error when opened twice' do
95
+ @sphinx.Open.should be_true
96
+ @sphinx.Open.should be_false
97
+ @sphinx.GetLastError.should == 'already connected'
98
+
99
+ socket = @sphinx.servers.first.instance_variable_get(:@socket)
100
+ socket.should be_kind_of(Sphinx::BufferedIO)
101
+ socket.close
102
+ end
103
+ end
104
+
104
105
  context 'in Close method' do
105
106
  it 'should open socket' do
106
107
  @sphinx.Open.should be_true
107
108
  @sphinx.Close.should be_true
108
- @sphinx.instance_variable_get(:@socket).should be_false
109
+ @sphinx.servers.first.instance_variable_get(:@socket).should be_nil
109
110
  end
110
-
111
+
111
112
  it 'should produce socket is closed' do
112
113
  @sphinx.Close.should be_false
113
114
  @sphinx.GetLastError.should == 'not connected'
114
- @sphinx.instance_variable_get(:@socket).should be_false
115
-
115
+ @sphinx.servers.first.instance_variable_get(:@socket).should be_nil
116
+
116
117
  @sphinx.Open.should be_true
117
118
  @sphinx.Close.should be_true
118
119
  @sphinx.Close.should be_false
119
120
  @sphinx.GetLastError.should == 'not connected'
120
- @sphinx.instance_variable_get(:@socket).should be_false
121
+ @sphinx.servers.first.instance_variable_get(:@socket).should be_nil
121
122
  end
122
123
  end
123
124
 
data/spec/client_spec.rb CHANGED
@@ -1,471 +1,650 @@
1
- require File.dirname(__FILE__) + '/../init'
2
-
3
- class SphinxSpecError < StandardError; end
4
-
5
- module SphinxFixtureHelper
6
- def sphinx_fixture(name)
7
- `php #{File.dirname(__FILE__)}/fixtures/#{name}.php`
8
- end
9
- end
10
-
11
- module SphinxApiCall
12
- def create_sphinx
13
- @sphinx = Sphinx::Client.new
14
- @sock = mock('TCPSocket')
15
- @sphinx.stub!(:Connect).and_return(@sock)
16
- @sphinx.stub!(:GetResponse).and_raise(SphinxSpecError)
17
- return @sphinx
18
- end
19
-
20
- def safe_call
21
- yield
22
- rescue SphinxSpecError
23
- end
24
- end
25
-
26
- describe 'The Connect method of Sphinx::Client' do
27
- before(:each) do
28
- @sphinx = Sphinx::Client.new
29
- @sock = mock('TCPSocket')
30
- end
31
-
32
- it 'should establish TCP connection to the server and initialize session' do
33
- TCPSocket.should_receive(:new).with('localhost', 3312).and_return(@sock)
34
- @sock.should_receive(:recv).with(4).and_return([1].pack('N'))
35
- @sock.should_receive(:send).with([1].pack('N'), 0)
36
- @sphinx.send(:Connect).should be(@sock)
37
- end
38
-
39
- it 'should raise exception when searchd protocol is not 1+' do
40
- TCPSocket.should_receive(:new).with('localhost', 3312).and_return(@sock)
41
- @sock.should_receive(:send).with([1].pack('N'), 0)
42
- @sock.should_receive(:recv).with(4).and_return([0].pack('N'))
43
- @sock.should_receive(:close)
44
- lambda { @sphinx.send(:Connect) }.should raise_error(Sphinx::SphinxConnectError)
45
- @sphinx.GetLastError.should == 'expected searchd protocol version 1+, got version \'0\''
46
- end
47
-
48
- it 'should raise exception on connection error' do
49
- TCPSocket.should_receive(:new).with('localhost', 3312).and_raise(Errno::EBADF)
50
- lambda { @sphinx.send(:Connect) }.should raise_error(Sphinx::SphinxConnectError)
51
- @sphinx.GetLastError.should == 'connection to localhost:3312 failed (errno=9, msg=Bad file descriptor)'
52
- end
53
-
54
- it 'should use custom host and port' do
55
- @sphinx.SetServer('anotherhost', 55555)
56
- TCPSocket.should_receive(:new).with('anotherhost', 55555).and_raise(Errno::EBADF)
57
- lambda { @sphinx.send(:Connect) }.should raise_error(Sphinx::SphinxConnectError)
58
- end
59
- end
60
-
61
- describe 'The GetResponse method of Sphinx::Client' do
62
- before(:each) do
63
- @sphinx = Sphinx::Client.new
64
- @sock = mock('TCPSocket')
65
- @sock.should_receive(:close)
66
- end
67
-
68
- it 'should receive response' do
69
- @sock.should_receive(:recv).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 4].pack('n2N'))
70
- @sock.should_receive(:recv).with(4).and_return([0].pack('N'))
71
- @sphinx.send(:GetResponse, @sock, 1)
72
- end
73
-
74
- it 'should raise exception on zero-sized response' do
75
- @sock.should_receive(:recv).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 0].pack('n2N'))
76
- lambda { @sphinx.send(:GetResponse, @sock, 1) }.should raise_error(Sphinx::SphinxResponseError)
77
- end
78
-
79
- it 'should raise exception when response is incomplete' do
80
- @sock.should_receive(:recv).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 4].pack('n2N'))
81
- @sock.should_receive(:recv).with(4).and_raise(EOFError)
82
- lambda { @sphinx.send(:GetResponse, @sock, 1) }.should raise_error(Sphinx::SphinxResponseError)
83
- end
84
-
85
- it 'should set warning message when SEARCHD_WARNING received' do
86
- @sock.should_receive(:recv).with(8).and_return([Sphinx::Client::SEARCHD_WARNING, 1, 14].pack('n2N'))
87
- @sock.should_receive(:recv).with(14).and_return([5].pack('N') + 'helloworld')
88
- @sphinx.send(:GetResponse, @sock, 1).should == 'world'
89
- @sphinx.GetLastWarning.should == 'hello'
90
- end
91
-
92
- it 'should raise exception when SEARCHD_ERROR received' do
93
- @sock.should_receive(:recv).with(8).and_return([Sphinx::Client::SEARCHD_ERROR, 1, 9].pack('n2N'))
94
- @sock.should_receive(:recv).with(9).and_return([1].pack('N') + 'hello')
95
- lambda { @sphinx.send(:GetResponse, @sock, 1) }.should raise_error(Sphinx::SphinxInternalError)
96
- @sphinx.GetLastError.should == 'searchd error: hello'
97
- end
98
-
99
- it 'should raise exception when SEARCHD_RETRY received' do
100
- @sock.should_receive(:recv).with(8).and_return([Sphinx::Client::SEARCHD_RETRY, 1, 9].pack('n2N'))
101
- @sock.should_receive(:recv).with(9).and_return([1].pack('N') + 'hello')
102
- lambda { @sphinx.send(:GetResponse, @sock, 1) }.should raise_error(Sphinx::SphinxTemporaryError)
103
- @sphinx.GetLastError.should == 'temporary searchd error: hello'
104
- end
105
-
106
- it 'should raise exception when unknown status received' do
107
- @sock.should_receive(:recv).with(8).and_return([65535, 1, 9].pack('n2N'))
108
- @sock.should_receive(:recv).with(9).and_return([1].pack('N') + 'hello')
109
- lambda { @sphinx.send(:GetResponse, @sock, 1) }.should raise_error(Sphinx::SphinxUnknownError)
110
- @sphinx.GetLastError.should == 'unknown status code: \'65535\''
111
- end
112
-
113
- it 'should set warning when server is older than client' do
114
- @sock.should_receive(:recv).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 9].pack('n2N'))
115
- @sock.should_receive(:recv).with(9).and_return([1].pack('N') + 'hello')
116
- @sphinx.send(:GetResponse, @sock, 5)
117
- @sphinx.GetLastWarning.should == 'searchd command v.0.1 older than client\'s v.0.5, some options might not work'
118
- end
119
- end
120
-
121
- describe 'The Query method of Sphinx::Client' do
122
- include SphinxFixtureHelper
123
- include SphinxApiCall
124
-
125
- before(:each) do
126
- @sphinx = create_sphinx
127
- end
128
-
129
- it 'should generate valid request with default parameters' do
130
- expected = sphinx_fixture('default_search')
131
- @sock.should_receive(:send).with(expected, 0)
132
- @sphinx.Query('query') rescue nil?
133
- end
134
-
135
- it 'should generate valid request with default parameters and index' do
136
- expected = sphinx_fixture('default_search_index')
137
- @sock.should_receive(:send).with(expected, 0)
138
- @sphinx.Query('query', 'index') rescue nil?
139
- end
140
-
141
- it 'should generate valid request with limits' do
142
- expected = sphinx_fixture('limits')
143
- @sock.should_receive(:send).with(expected, 0)
144
- @sphinx.SetLimits(10, 20)
145
- @sphinx.Query('query') rescue nil?
146
- end
147
-
148
- it 'should generate valid request with limits and max number to retrieve' do
149
- expected = sphinx_fixture('limits_max')
150
- @sock.should_receive(:send).with(expected, 0)
151
- @sphinx.SetLimits(10, 20, 30)
152
- @sphinx.Query('query') rescue nil?
153
- end
154
-
155
- it 'should generate valid request with limits and cutoff to retrieve' do
156
- expected = sphinx_fixture('limits_cutoff')
157
- @sock.should_receive(:send).with(expected, 0)
158
- @sphinx.SetLimits(10, 20, 30, 40)
159
- @sphinx.Query('query') rescue nil?
160
- end
161
-
162
- it 'should generate valid request with max query time specified' do
163
- expected = sphinx_fixture('max_query_time')
164
- @sock.should_receive(:send).with(expected, 0)
165
- @sphinx.SetMaxQueryTime(1000)
166
- @sphinx.Query('query') rescue nil?
167
- end
168
-
169
- describe 'with match' do
170
- [ :all, :any, :phrase, :boolean, :extended, :fullscan, :extended2 ].each do |match|
171
- it "should generate valid request for SPH_MATCH_#{match.to_s.upcase}" do
172
- expected = sphinx_fixture("match_#{match}")
173
- @sock.should_receive(:send).with(expected, 0)
174
- @sphinx.SetMatchMode(Sphinx::Client::const_get("SPH_MATCH_#{match.to_s.upcase}"))
175
- @sphinx.Query('query') rescue nil?
176
- end
177
- end
178
- end
179
-
180
- describe 'with rank' do
181
- [ :proximity_bm25, :bm25, :none, :wordcount, :proximity, :matchany, :fieldmask, :sph04 ].each do |rank|
182
- it "should generate valid request for SPH_RANK_#{rank.to_s.upcase}" do
183
- expected = sphinx_fixture("ranking_#{rank}")
184
- @sock.should_receive(:send).with(expected, 0)
185
- @sphinx.SetRankingMode(Sphinx::Client.const_get("SPH_RANK_#{rank.to_s.upcase}"))
186
- @sphinx.Query('query') rescue nil?
187
- end
188
- end
189
- end
190
-
191
- describe 'with sorting' do
192
- [ :attr_desc, :relevance, :attr_asc, :time_segments, :extended, :expr ].each do |mode|
193
- it "should generate valid request for SPH_SORT_#{mode.to_s.upcase}" do
194
- expected = sphinx_fixture("sort_#{mode}")
195
- @sock.should_receive(:send).with(expected, 0)
196
- @sphinx.SetSortMode(Sphinx::Client.const_get("SPH_SORT_#{mode.to_s.upcase}"), mode == :relevance ? '' : 'sortby')
197
- @sphinx.Query('query') rescue nil?
198
- end
199
- end
200
- end
201
-
202
- it 'should generate valid request with weights' do
203
- expected = sphinx_fixture('weights')
204
- @sock.should_receive(:send).with(expected, 0)
205
- @sphinx.SetWeights([10, 20, 30, 40])
206
- @sphinx.Query('query') rescue nil?
207
- end
208
-
209
- it 'should generate valid request with field weights' do
210
- expected = sphinx_fixture('field_weights')
211
- @sock.should_receive(:send).with(expected, 0)
212
- @sphinx.SetFieldWeights({'field1' => 10, 'field2' => 20})
213
- @sphinx.Query('query') rescue nil?
214
- end
215
-
216
- it 'should generate valid request with index weights' do
217
- expected = sphinx_fixture('index_weights')
218
- @sock.should_receive(:send).with(expected, 0)
219
- @sphinx.SetIndexWeights({'index1' => 10, 'index2' => 20})
220
- @sphinx.Query('query') rescue nil?
221
- end
222
-
223
- it 'should generate valid request with ID range' do
224
- expected = sphinx_fixture('id_range')
225
- @sock.should_receive(:send).with(expected, 0)
226
- @sphinx.SetIDRange(10, 20)
227
- @sphinx.Query('query') rescue nil?
228
- end
229
-
230
- it 'should generate valid request with ID range and 64-bit ints' do
231
- expected = sphinx_fixture('id_range64')
232
- @sock.should_receive(:send).with(expected, 0)
233
- @sphinx.SetIDRange(8589934591, 17179869183)
234
- @sphinx.Query('query') rescue nil?
235
- end
236
-
237
- it 'should generate valid request with values filter' do
238
- expected = sphinx_fixture('filter')
239
- @sock.should_receive(:send).with(expected, 0)
240
- @sphinx.SetFilter('attr', [10, 20, 30])
241
- @sphinx.Query('query') rescue nil?
242
- end
243
-
244
- it 'should generate valid request with two values filters' do
245
- expected = sphinx_fixture('filters')
246
- @sock.should_receive(:send).with(expected, 0)
247
- @sphinx.SetFilter('attr2', [40, 50])
248
- @sphinx.SetFilter('attr1', [10, 20, 30])
249
- @sphinx.Query('query') rescue nil?
250
- end
251
-
252
- it 'should generate valid request with values filter excluded' do
253
- expected = sphinx_fixture('filter_exclude')
254
- @sock.should_receive(:send).with(expected, 0)
255
- @sphinx.SetFilter('attr', [10, 20, 30], true)
256
- @sphinx.Query('query') rescue nil?
257
- end
258
-
259
- it 'should generate valid request with values filter range' do
260
- expected = sphinx_fixture('filter_range')
261
- @sock.should_receive(:send).with(expected, 0)
262
- @sphinx.SetFilterRange('attr', 10, 20)
263
- @sphinx.Query('query') rescue nil?
264
- end
265
-
266
- it 'should generate valid request with two filter ranges' do
267
- expected = sphinx_fixture('filter_ranges')
268
- @sock.should_receive(:send).with(expected, 0)
269
- @sphinx.SetFilterRange('attr2', 30, 40)
270
- @sphinx.SetFilterRange('attr1', 10, 20)
271
- @sphinx.Query('query') rescue nil?
272
- end
273
-
274
- it 'should generate valid request with filter range excluded' do
275
- expected = sphinx_fixture('filter_range_exclude')
276
- @sock.should_receive(:send).with(expected, 0)
277
- @sphinx.SetFilterRange('attr', 10, 20, true)
278
- @sphinx.Query('query') rescue nil?
279
- end
280
-
281
- it 'should generate valid request with signed int64-based filter range' do
282
- expected = sphinx_fixture('filter_range_int64')
283
- @sock.should_receive(:send).with(expected, 0)
284
- @sphinx.SetFilterRange('attr1', -10, 20)
285
- @sphinx.SetFilterRange('attr2', -1099511627770, 1099511627780)
286
- safe_call { @sphinx.Query('query') }
287
- end
288
-
289
- it 'should generate valid request with float filter range' do
290
- expected = sphinx_fixture('filter_float_range')
291
- @sock.should_receive(:send).with(expected, 0)
292
- @sphinx.SetFilterFloatRange('attr', 10.5, 20.3)
293
- @sphinx.Query('query') rescue nil?
294
- end
295
-
296
- it 'should generate valid request with float filter excluded' do
297
- expected = sphinx_fixture('filter_float_range_exclude')
298
- @sock.should_receive(:send).with(expected, 0)
299
- @sphinx.SetFilterFloatRange('attr', 10.5, 20.3, true)
300
- @sphinx.Query('query') rescue nil?
301
- end
302
-
303
- it 'should generate valid request with different filters' do
304
- expected = sphinx_fixture('filters_different')
305
- @sock.should_receive(:send).with(expected, 0)
306
- @sphinx.SetFilterRange('attr1', 10, 20, true)
307
- @sphinx.SetFilter('attr3', [30, 40, 50])
308
- @sphinx.SetFilterRange('attr1', 60, 70)
309
- @sphinx.SetFilter('attr2', [80, 90, 100], true)
310
- @sphinx.SetFilterFloatRange('attr1', 60.8, 70.5)
311
- @sphinx.Query('query') rescue nil?
312
- end
313
-
314
- it 'should generate valid request with geographical anchor point' do
315
- expected = sphinx_fixture('geo_anchor')
316
- @sock.should_receive(:send).with(expected, 0)
317
- @sphinx.SetGeoAnchor('attrlat', 'attrlong', 20.3, 40.7)
318
- @sphinx.Query('query') rescue nil?
319
- end
320
-
321
- describe 'with group by' do
322
- [ :day, :week, :month, :year, :attr, :attrpair ].each do |groupby|
323
- it "should generate valid request for SPH_GROUPBY_#{groupby.to_s.upcase}" do
324
- expected = sphinx_fixture("group_by_#{groupby}")
325
- @sock.should_receive(:send).with(expected, 0)
326
- @sphinx.SetGroupBy('attr', Sphinx::Client::const_get("SPH_GROUPBY_#{groupby.to_s.upcase}"))
327
- @sphinx.Query('query') rescue nil?
328
- end
329
- end
330
-
331
- it 'should generate valid request for SPH_GROUPBY_DAY with sort' do
332
- expected = sphinx_fixture('group_by_day_sort')
333
- @sock.should_receive(:send).with(expected, 0)
334
- @sphinx.SetGroupBy('attr', Sphinx::Client::SPH_GROUPBY_DAY, 'somesort')
335
- @sphinx.Query('query') rescue nil?
336
- end
337
-
338
- it 'should generate valid request with count-distinct attribute' do
339
- expected = sphinx_fixture('group_distinct')
340
- @sock.should_receive(:send).with(expected, 0)
341
- @sphinx.SetGroupBy('attr', Sphinx::Client::SPH_GROUPBY_DAY)
342
- @sphinx.SetGroupDistinct('attr')
343
- @sphinx.Query('query') rescue nil?
344
- end
345
- end
346
-
347
- it 'should generate valid request with retries count specified' do
348
- expected = sphinx_fixture('retries')
349
- @sock.should_receive(:send).with(expected, 0)
350
- @sphinx.SetRetries(10)
351
- @sphinx.Query('query') rescue nil?
352
- end
353
-
354
- it 'should generate valid request with retries count and delay specified' do
355
- expected = sphinx_fixture('retries_delay')
356
- @sock.should_receive(:send).with(expected, 0)
357
- @sphinx.SetRetries(10, 20)
358
- @sphinx.Query('query') rescue nil?
359
- end
360
-
361
- it 'should generate valid request for SetOverride' do
362
- expected = sphinx_fixture('set_override')
363
- @sock.should_receive(:send).with(expected, 0)
364
- @sphinx.SetOverride('attr1', Sphinx::Client::SPH_ATTR_INTEGER, { 10 => 20 })
365
- @sphinx.SetOverride('attr2', Sphinx::Client::SPH_ATTR_FLOAT, { 11 => 30.3 })
366
- @sphinx.SetOverride('attr3', Sphinx::Client::SPH_ATTR_BIGINT, { 12 => 1099511627780 })
367
- @sphinx.Query('query') rescue nil?
368
- end
369
-
370
- it 'should generate valid request for SetSelect' do
371
- expected = sphinx_fixture('select')
372
- @sock.should_receive(:send).with(expected, 0)
373
- @sphinx.SetSelect('attr1, attr2')
374
- @sphinx.Query('query') rescue nil?
375
- end
376
- end
377
-
378
- describe 'The RunQueries method of Sphinx::Client' do
379
- include SphinxFixtureHelper
380
-
381
- before(:each) do
382
- @sphinx = Sphinx::Client.new
383
- @sock = mock('TCPSocket')
384
- @sphinx.stub!(:Connect).and_return(@sock)
385
- @sphinx.stub!(:GetResponse).and_raise(Sphinx::SphinxError)
386
- end
387
-
388
- it 'should generate valid request for multiple queries' do
389
- expected = sphinx_fixture('miltiple_queries')
390
- @sock.should_receive(:send).with(expected, 0)
391
-
392
- @sphinx.SetRetries(10, 20)
393
- @sphinx.AddQuery('test1')
394
- @sphinx.SetGroupBy('attr', Sphinx::Client::SPH_GROUPBY_DAY)
395
- @sphinx.AddQuery('test2') rescue nil?
396
-
397
- @sphinx.RunQueries rescue nil?
398
- end
399
- end
400
-
401
- describe 'The BuildExcerpts method of Sphinx::Client' do
402
- include SphinxFixtureHelper
403
-
404
- before(:each) do
405
- @sphinx = Sphinx::Client.new
406
- @sock = mock('TCPSocket')
407
- @sphinx.stub!(:Connect).and_return(@sock)
408
- @sphinx.stub!(:GetResponse).and_raise(Sphinx::SphinxError)
409
- end
410
-
411
- it 'should generate valid request with default parameters' do
412
- expected = sphinx_fixture('excerpt_default')
413
- @sock.should_receive(:send).with(expected, 0)
414
- @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2') rescue nil?
415
- end
416
-
417
- it 'should generate valid request with custom parameters' do
418
- expected = sphinx_fixture('excerpt_custom')
419
- @sock.should_receive(:send).with(expected, 0)
420
- @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2', { 'before_match' => 'before',
421
- 'after_match' => 'after',
422
- 'chunk_separator' => 'separator',
423
- 'limit' => 10 }) rescue nil?
424
- end
425
-
426
- it 'should generate valid request with flags' do
427
- expected = sphinx_fixture('excerpt_flags')
428
- @sock.should_receive(:send).with(expected, 0)
429
- @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2', { 'exact_phrase' => true,
430
- 'single_passage' => true,
431
- 'use_boundaries' => true,
432
- 'weight_order' => true,
433
- 'query_mode' => true }) rescue nil?
434
- end
435
- end
436
-
437
- describe 'The BuildKeywords method of Sphinx::Client' do
438
- include SphinxFixtureHelper
439
- include SphinxApiCall
440
-
441
- before(:each) do
442
- @sphinx = create_sphinx
443
- end
444
-
445
- it 'should generate valid request' do
446
- expected = sphinx_fixture('keywords')
447
- @sock.should_receive(:send).with(expected, 0)
448
- safe_call { @sphinx.BuildKeywords('test', 'index', true) }
449
- end
450
- end
451
-
452
- describe 'The UpdateAttributes method of Sphinx::Client' do
453
- include SphinxFixtureHelper
454
- include SphinxApiCall
455
-
456
- before(:each) do
457
- @sphinx = create_sphinx
458
- end
459
-
460
- it 'should generate valid request' do
461
- expected = sphinx_fixture('update_attributes')
462
- @sock.should_receive(:send).with(expected, 0)
463
- safe_call { @sphinx.UpdateAttributes('index', ['group'], { 123 => [456] }) }
464
- end
465
-
466
- it 'should generate valid request for MVA' do
467
- expected = sphinx_fixture('update_attributes_mva')
468
- @sock.should_receive(:send).with(expected, 0)
469
- safe_call { @sphinx.UpdateAttributes('index', ['group', 'category'], { 123 => [ [456, 789], [1, 2, 3] ] }, true) }
470
- end
471
- end
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 round-robin servers on each call' do
30
+ @sphinx.SetServers(@servers)
31
+ cnt = 0
32
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[0] }
33
+ cnt.should == 1
34
+ cnt = 0
35
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[1] }
36
+ cnt.should == 1
37
+ cnt = 0
38
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[0] }
39
+ cnt.should == 1
40
+ end
41
+
42
+ it 'should round-robin servers and raise an exception on error' do
43
+ @sphinx.SetServers(@servers)
44
+ cnt = 0
45
+ expect {
46
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[0]; raise Sphinx::SphinxConnectError }
47
+ }.to raise_error(Sphinx::SphinxConnectError)
48
+ cnt.should == 1
49
+ cnt = 0
50
+ expect {
51
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[1]; raise Sphinx::SphinxConnectError }
52
+ }.to raise_error(Sphinx::SphinxConnectError)
53
+ cnt.should == 1
54
+ cnt = 0
55
+ expect {
56
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[0]; raise Sphinx::SphinxConnectError }
57
+ }.to raise_error(Sphinx::SphinxConnectError)
58
+ cnt.should == 1
59
+ end
60
+ end
61
+
62
+ context 'with retries' do
63
+ before :each do
64
+ @sphinx.SetConnectTimeout(0, 3)
65
+ end
66
+
67
+ it 'should raise an exception on error' do
68
+ cnt = 0
69
+ expect {
70
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[0]; raise Sphinx::SphinxConnectError }
71
+ }.to raise_error(Sphinx::SphinxConnectError)
72
+ cnt.should == 3
73
+ end
74
+
75
+ it 'should round-robin servers and raise an exception on error' do
76
+ @sphinx.SetServers(@servers)
77
+ cnt = 0
78
+ expect {
79
+ @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[(cnt - 1) % 2]; raise Sphinx::SphinxConnectError }
80
+ }.to raise_error(Sphinx::SphinxConnectError)
81
+ cnt.should == 3
82
+ end
83
+ end
84
+ end
85
+
86
+ context 'in with_socket method' do
87
+ before :each do
88
+ @sphinx = Sphinx::Client.new
89
+ @socket = mock('TCPSocket')
90
+ end
91
+
92
+ context 'without retries' do
93
+ before :each do
94
+ @server = mock('Server')
95
+ @server.should_receive(:get_socket).and_yield(@socket).and_return(@socket)
96
+ @server.should_receive(:free_socket).with(@socket).at_least(1)
97
+ end
98
+
99
+ it 'should initialize session' do
100
+ @socket.should_receive(:write).with([1].pack('N'))
101
+ @socket.should_receive(:read).with(4).and_return([1].pack('N'))
102
+ cnt = 0
103
+ @sphinx.send(:with_socket, @server) { |socket| cnt += 1; socket.should == @socket }
104
+ cnt.should == 1
105
+ end
106
+
107
+ it 'should raise exception when searchd protocol is not 1+' do
108
+ @socket.should_receive(:write).with([1].pack('N'))
109
+ @socket.should_receive(:read).with(4).and_return([0].pack('N'))
110
+ cnt = 0
111
+ expect {
112
+ @sphinx.send(:with_socket, @server) { cnt += 1; }
113
+ }.to raise_error(Sphinx::SphinxConnectError, 'expected searchd protocol version 1+, got version \'0\'')
114
+ cnt.should == 0
115
+ end
116
+
117
+ it 'should handle request timeouts' do
118
+ @socket.should_receive(:write).with([1].pack('N'))
119
+ @socket.should_receive(:read).with(4).and_return([1].pack('N'))
120
+ @sphinx.SetRequestTimeout(1)
121
+ cnt = 0
122
+ expect {
123
+ @sphinx.send(:with_socket, @server) { cnt += 1; sleep 2 }
124
+ }.to raise_error(Sphinx::SphinxResponseError, 'failed to read searchd response (msg=time\'s up!)')
125
+ cnt.should == 1
126
+
127
+ @sphinx.GetLastError.should == 'failed to read searchd response (msg=time\'s up!)'
128
+ @sphinx.IsConnectError.should be_false
129
+ end
130
+
131
+ it 'should re-reaise Sphinx errors' do
132
+ @socket.should_receive(:write).with([1].pack('N'))
133
+ @socket.should_receive(:read).with(4).and_return([1].pack('N'))
134
+ cnt = 0
135
+ expect {
136
+ @sphinx.send(:with_socket, @server) { cnt += 1; raise Sphinx::SphinxInternalError, 'hello' }
137
+ }.to raise_error(Sphinx::SphinxInternalError, 'hello')
138
+ cnt.should == 1
139
+
140
+ @sphinx.GetLastError.should == 'hello'
141
+ @sphinx.IsConnectError.should be_false
142
+ end
143
+ end
144
+
145
+ context 'with retries' do
146
+ before :each do
147
+ @sphinx.SetRequestTimeout(0, 3)
148
+ # two more times yielding - retries
149
+ @server = mock('Server')
150
+ @server.should_receive(:get_socket).at_least(1).times.and_yield(@socket).and_return(@socket)
151
+ @server.should_receive(:free_socket).with(@socket).at_least(1)
152
+ end
153
+
154
+ it 'should raise an exception on error' do
155
+ @socket.should_receive(:write).exactly(3).times.with([1].pack('N'))
156
+ @socket.should_receive(:read).exactly(3).times.with(4).and_return([1].pack('N'))
157
+ cnt = 0
158
+ expect {
159
+ @sphinx.send(:with_socket, @server) { cnt += 1; raise Sphinx::SphinxInternalError, 'hello' }
160
+ }.to raise_error(Sphinx::SphinxInternalError, 'hello')
161
+ cnt.should == 3
162
+
163
+ @sphinx.GetLastError.should == 'hello'
164
+ @sphinx.IsConnectError.should be_false
165
+ end
166
+ end
167
+ end
168
+
169
+ context 'in parse_response method' do
170
+ before :each do
171
+ @sphinx = Sphinx::Client.new
172
+ @socket = mock('TCPSocket')
173
+ end
174
+
175
+ it 'should receive response' do
176
+ @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 4].pack('n2N'))
177
+ @socket.should_receive(:read).with(4).and_return([0].pack('N'))
178
+ @sphinx.send(:parse_response, @socket, 1)
179
+ end
180
+
181
+ it 'should raise exception on zero-sized response' do
182
+ @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 0].pack('n2N'))
183
+ expect {
184
+ @sphinx.send(:parse_response, @socket, 1)
185
+ }.to raise_error(Sphinx::SphinxResponseError, 'received zero-sized searchd response')
186
+ end
187
+
188
+ it 'should raise exception when response is incomplete' do
189
+ @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 4].pack('n2N'))
190
+ @socket.should_receive(:read).with(4).and_return('')
191
+ expect {
192
+ @sphinx.send(:parse_response, @socket, 1)
193
+ }.to raise_error(Sphinx::SphinxResponseError)
194
+ end
195
+
196
+ it 'should set warning message when SEARCHD_WARNING received' do
197
+ @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_WARNING, 1, 14].pack('n2N'))
198
+ @socket.should_receive(:read).with(14).and_return([5].pack('N') + 'helloworld')
199
+ @sphinx.send(:parse_response, @socket, 1).should == 'world'
200
+ @sphinx.GetLastWarning.should == 'hello'
201
+ end
202
+
203
+ it 'should raise exception when SEARCHD_ERROR received' do
204
+ @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_ERROR, 1, 9].pack('n2N'))
205
+ @socket.should_receive(:read).with(9).and_return([1].pack('N') + 'hello')
206
+ expect {
207
+ @sphinx.send(:parse_response, @socket, 1)
208
+ }.to raise_error(Sphinx::SphinxInternalError, 'searchd error: hello')
209
+ end
210
+
211
+ it 'should raise exception when SEARCHD_RETRY received' do
212
+ @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_RETRY, 1, 9].pack('n2N'))
213
+ @socket.should_receive(:read).with(9).and_return([1].pack('N') + 'hello')
214
+ expect {
215
+ @sphinx.send(:parse_response, @socket, 1)
216
+ }.to raise_error(Sphinx::SphinxTemporaryError, 'temporary searchd error: hello')
217
+ end
218
+
219
+ it 'should raise exception when unknown status received' do
220
+ @socket.should_receive(:read).with(8).and_return([65535, 1, 9].pack('n2N'))
221
+ @socket.should_receive(:read).with(9).and_return([1].pack('N') + 'hello')
222
+ expect {
223
+ @sphinx.send(:parse_response, @socket, 1)
224
+ }.to raise_error(Sphinx::SphinxUnknownError, 'unknown status code: \'65535\'')
225
+ end
226
+
227
+ it 'should set warning when server is older than client' do
228
+ @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 9].pack('n2N'))
229
+ @socket.should_receive(:read).with(9).and_return([1].pack('N') + 'hello')
230
+ @sphinx.send(:parse_response, @socket, 5)
231
+ @sphinx.GetLastWarning.should == 'searchd command v.0.1 older than client\'s v.0.5, some options might not work'
232
+ end
233
+ end
234
+
235
+ context 'in Query method' do
236
+ before :each do
237
+ @sphinx = sphinx_create_client
238
+ end
239
+
240
+ it 'should generate valid request with default parameters' do
241
+ expected = sphinx_fixture('default_search')
242
+ @sock.should_receive(:write).with(expected)
243
+ sphinx_safe_call { @sphinx.Query('query') }
244
+ end
245
+
246
+ it 'should generate valid request with default parameters and index' do
247
+ expected = sphinx_fixture('default_search_index')
248
+ @sock.should_receive(:write).with(expected)
249
+ sphinx_safe_call { @sphinx.Query('query', 'index') }
250
+ end
251
+
252
+ it 'should generate valid request with limits' do
253
+ expected = sphinx_fixture('limits')
254
+ @sock.should_receive(:write).with(expected)
255
+ @sphinx.SetLimits(10, 20)
256
+ sphinx_safe_call { @sphinx.Query('query') }
257
+ end
258
+
259
+ it 'should generate valid request with limits and max number to retrieve' do
260
+ expected = sphinx_fixture('limits_max')
261
+ @sock.should_receive(:write).with(expected)
262
+ @sphinx.SetLimits(10, 20, 30)
263
+ sphinx_safe_call { @sphinx.Query('query') }
264
+ end
265
+
266
+ it 'should generate valid request with limits and cutoff to retrieve' do
267
+ expected = sphinx_fixture('limits_cutoff')
268
+ @sock.should_receive(:write).with(expected)
269
+ @sphinx.SetLimits(10, 20, 30, 40)
270
+ sphinx_safe_call { @sphinx.Query('query') }
271
+ end
272
+
273
+ it 'should generate valid request with max query time specified' do
274
+ expected = sphinx_fixture('max_query_time')
275
+ @sock.should_receive(:write).with(expected)
276
+ @sphinx.SetMaxQueryTime(1000)
277
+ sphinx_safe_call { @sphinx.Query('query') }
278
+ end
279
+
280
+ describe 'with match' do
281
+ [ :all, :any, :phrase, :boolean, :extended, :fullscan, :extended2 ].each do |match|
282
+ it "should generate valid request for SPH_MATCH_#{match.to_s.upcase}" do
283
+ expected = sphinx_fixture("match_#{match}")
284
+ @sock.should_receive(:write).with(expected)
285
+ @sphinx.SetMatchMode(Sphinx::Client::const_get("SPH_MATCH_#{match.to_s.upcase}"))
286
+ sphinx_safe_call { @sphinx.Query('query') }
287
+ end
288
+
289
+ it "should generate valid request for \"#{match}\"" do
290
+ expected = sphinx_fixture("match_#{match}")
291
+ @sock.should_receive(:write).with(expected)
292
+ @sphinx.SetMatchMode(match.to_s)
293
+ sphinx_safe_call { @sphinx.Query('query') }
294
+ end
295
+
296
+ it "should generate valid request for :#{match}" do
297
+ expected = sphinx_fixture("match_#{match}")
298
+ @sock.should_receive(:write).with(expected)
299
+ @sphinx.SetMatchMode(match)
300
+ sphinx_safe_call { @sphinx.Query('query') }
301
+ end
302
+ end
303
+ end
304
+
305
+ describe 'with rank' do
306
+ [ :proximity_bm25, :bm25, :none, :wordcount, :proximity, :matchany, :fieldmask, :sph04 ].each do |rank|
307
+ it "should generate valid request for SPH_RANK_#{rank.to_s.upcase}" do
308
+ expected = sphinx_fixture("ranking_#{rank}")
309
+ @sock.should_receive(:write).with(expected)
310
+ @sphinx.SetRankingMode(Sphinx::Client.const_get("SPH_RANK_#{rank.to_s.upcase}"))
311
+ sphinx_safe_call { @sphinx.Query('query') }
312
+ end
313
+
314
+ it "should generate valid request for \"#{rank}\"" do
315
+ expected = sphinx_fixture("ranking_#{rank}")
316
+ @sock.should_receive(:write).with(expected)
317
+ @sphinx.SetRankingMode(rank.to_s)
318
+ sphinx_safe_call { @sphinx.Query('query') }
319
+ end
320
+
321
+ it "should generate valid request for :#{rank}" do
322
+ expected = sphinx_fixture("ranking_#{rank}")
323
+ @sock.should_receive(:write).with(expected)
324
+ @sphinx.SetRankingMode(rank)
325
+ sphinx_safe_call { @sphinx.Query('query') }
326
+ end
327
+ end
328
+ end
329
+
330
+ describe 'with sorting' do
331
+ [ :attr_desc, :relevance, :attr_asc, :time_segments, :extended, :expr ].each do |mode|
332
+ it "should generate valid request for SPH_SORT_#{mode.to_s.upcase}" do
333
+ expected = sphinx_fixture("sort_#{mode}")
334
+ @sock.should_receive(:write).with(expected)
335
+ @sphinx.SetSortMode(Sphinx::Client.const_get("SPH_SORT_#{mode.to_s.upcase}"), mode == :relevance ? '' : 'sortby')
336
+ sphinx_safe_call { @sphinx.Query('query') }
337
+ end
338
+
339
+ it "should generate valid request for \"#{mode}\"" do
340
+ expected = sphinx_fixture("sort_#{mode}")
341
+ @sock.should_receive(:write).with(expected)
342
+ @sphinx.SetSortMode(mode.to_s, mode == :relevance ? '' : 'sortby')
343
+ sphinx_safe_call { @sphinx.Query('query') }
344
+ end
345
+
346
+ it "should generate valid request for :#{mode}" do
347
+ expected = sphinx_fixture("sort_#{mode}")
348
+ @sock.should_receive(:write).with(expected)
349
+ @sphinx.SetSortMode(mode, mode == :relevance ? '' : 'sortby')
350
+ sphinx_safe_call { @sphinx.Query('query') }
351
+ end
352
+ end
353
+ end
354
+
355
+ it 'should generate valid request with weights' do
356
+ expected = sphinx_fixture('weights')
357
+ @sock.should_receive(:write).with(expected)
358
+ @sphinx.SetWeights([10, 20, 30, 40])
359
+ sphinx_safe_call { @sphinx.Query('query') }
360
+ end
361
+
362
+ it 'should generate valid request with field weights' do
363
+ expected = sphinx_fixture('field_weights')
364
+ @sock.should_receive(:write).with(expected)
365
+ @sphinx.SetFieldWeights({'field1' => 10, 'field2' => 20})
366
+ sphinx_safe_call { @sphinx.Query('query') }
367
+ end
368
+
369
+ it 'should generate valid request with index weights' do
370
+ expected = sphinx_fixture('index_weights')
371
+ @sock.should_receive(:write).with(expected)
372
+ @sphinx.SetIndexWeights({'index1' => 10, 'index2' => 20})
373
+ sphinx_safe_call { @sphinx.Query('query') }
374
+ end
375
+
376
+ it 'should generate valid request with ID range' do
377
+ expected = sphinx_fixture('id_range')
378
+ @sock.should_receive(:write).with(expected)
379
+ @sphinx.SetIDRange(10, 20)
380
+ sphinx_safe_call { @sphinx.Query('query') }
381
+ end
382
+
383
+ it 'should generate valid request with ID range and 64-bit ints' do
384
+ expected = sphinx_fixture('id_range64')
385
+ @sock.should_receive(:write).with(expected)
386
+ @sphinx.SetIDRange(8589934591, 17179869183)
387
+ sphinx_safe_call { @sphinx.Query('query') }
388
+ end
389
+
390
+ it 'should generate valid request with values filter' do
391
+ expected = sphinx_fixture('filter')
392
+ @sock.should_receive(:write).with(expected)
393
+ @sphinx.SetFilter('attr', [10, 20, 30])
394
+ sphinx_safe_call { @sphinx.Query('query') }
395
+ end
396
+
397
+ it 'should generate valid request with two values filters' do
398
+ expected = sphinx_fixture('filters')
399
+ @sock.should_receive(:write).with(expected)
400
+ @sphinx.SetFilter('attr2', [40, 50])
401
+ @sphinx.SetFilter('attr1', [10, 20, 30])
402
+ sphinx_safe_call { @sphinx.Query('query') }
403
+ end
404
+
405
+ it 'should generate valid request with values filter excluded' do
406
+ expected = sphinx_fixture('filter_exclude')
407
+ @sock.should_receive(:write).with(expected)
408
+ @sphinx.SetFilter('attr', [10, 20, 30], true)
409
+ sphinx_safe_call { @sphinx.Query('query') }
410
+ end
411
+
412
+ it 'should generate valid request with values filter range' do
413
+ expected = sphinx_fixture('filter_range')
414
+ @sock.should_receive(:write).with(expected)
415
+ @sphinx.SetFilterRange('attr', 10, 20)
416
+ sphinx_safe_call { @sphinx.Query('query') }
417
+ end
418
+
419
+ it 'should generate valid request with two filter ranges' do
420
+ expected = sphinx_fixture('filter_ranges')
421
+ @sock.should_receive(:write).with(expected)
422
+ @sphinx.SetFilterRange('attr2', 30, 40)
423
+ @sphinx.SetFilterRange('attr1', 10, 20)
424
+ sphinx_safe_call { @sphinx.Query('query') }
425
+ end
426
+
427
+ it 'should generate valid request with filter range excluded' do
428
+ expected = sphinx_fixture('filter_range_exclude')
429
+ @sock.should_receive(:write).with(expected)
430
+ @sphinx.SetFilterRange('attr', 10, 20, true)
431
+ sphinx_safe_call { @sphinx.Query('query') }
432
+ end
433
+
434
+ it 'should generate valid request with signed int64-based filter range' do
435
+ expected = sphinx_fixture('filter_range_int64')
436
+ @sock.should_receive(:write).with(expected)
437
+ @sphinx.SetFilterRange('attr1', -10, 20)
438
+ @sphinx.SetFilterRange('attr2', -1099511627770, 1099511627780)
439
+ sphinx_safe_call { @sphinx.Query('query') }
440
+ end
441
+
442
+ it 'should generate valid request with float filter range' do
443
+ expected = sphinx_fixture('filter_float_range')
444
+ @sock.should_receive(:write).with(expected)
445
+ @sphinx.SetFilterFloatRange('attr', 10.5, 20.3)
446
+ sphinx_safe_call { @sphinx.Query('query') }
447
+ end
448
+
449
+ it 'should generate valid request with float filter excluded' do
450
+ expected = sphinx_fixture('filter_float_range_exclude')
451
+ @sock.should_receive(:write).with(expected)
452
+ @sphinx.SetFilterFloatRange('attr', 10.5, 20.3, true)
453
+ sphinx_safe_call { @sphinx.Query('query') }
454
+ end
455
+
456
+ it 'should generate valid request with different filters' do
457
+ expected = sphinx_fixture('filters_different')
458
+ @sock.should_receive(:write).with(expected)
459
+ @sphinx.SetFilterRange('attr1', 10, 20, true)
460
+ @sphinx.SetFilter('attr3', [30, 40, 50])
461
+ @sphinx.SetFilterRange('attr1', 60, 70)
462
+ @sphinx.SetFilter('attr2', [80, 90, 100], true)
463
+ @sphinx.SetFilterFloatRange('attr1', 60.8, 70.5)
464
+ sphinx_safe_call { @sphinx.Query('query') }
465
+ end
466
+
467
+ it 'should generate valid request with geographical anchor point' do
468
+ expected = sphinx_fixture('geo_anchor')
469
+ @sock.should_receive(:write).with(expected)
470
+ @sphinx.SetGeoAnchor('attrlat', 'attrlong', 20.3, 40.7)
471
+ sphinx_safe_call { @sphinx.Query('query') }
472
+ end
473
+
474
+ describe 'with group by' do
475
+ [ :day, :week, :month, :year, :attr, :attrpair ].each do |groupby|
476
+ it "should generate valid request for SPH_GROUPBY_#{groupby.to_s.upcase}" do
477
+ expected = sphinx_fixture("group_by_#{groupby}")
478
+ @sock.should_receive(:write).with(expected)
479
+ @sphinx.SetGroupBy('attr', Sphinx::Client::const_get("SPH_GROUPBY_#{groupby.to_s.upcase}"))
480
+ sphinx_safe_call { @sphinx.Query('query') }
481
+ end
482
+
483
+ it "should generate valid request for \"#{groupby}\"" do
484
+ expected = sphinx_fixture("group_by_#{groupby}")
485
+ @sock.should_receive(:write).with(expected)
486
+ @sphinx.SetGroupBy('attr', groupby.to_s)
487
+ sphinx_safe_call { @sphinx.Query('query') }
488
+ end
489
+
490
+ it "should generate valid request for :#{groupby}" do
491
+ expected = sphinx_fixture("group_by_#{groupby}")
492
+ @sock.should_receive(:write).with(expected)
493
+ @sphinx.SetGroupBy('attr', groupby)
494
+ sphinx_safe_call { @sphinx.Query('query') }
495
+ end
496
+ end
497
+
498
+ it 'should generate valid request for SPH_GROUPBY_DAY with sort' do
499
+ expected = sphinx_fixture('group_by_day_sort')
500
+ @sock.should_receive(:write).with(expected)
501
+ @sphinx.SetGroupBy('attr', Sphinx::Client::SPH_GROUPBY_DAY, 'somesort')
502
+ sphinx_safe_call { @sphinx.Query('query') }
503
+ end
504
+
505
+ it 'should generate valid request with count-distinct attribute' do
506
+ expected = sphinx_fixture('group_distinct')
507
+ @sock.should_receive(:write).with(expected)
508
+ @sphinx.SetGroupBy('attr', Sphinx::Client::SPH_GROUPBY_DAY)
509
+ @sphinx.SetGroupDistinct('attr')
510
+ sphinx_safe_call { @sphinx.Query('query') }
511
+ end
512
+ end
513
+
514
+ it 'should generate valid request with retries count specified' do
515
+ expected = sphinx_fixture('retries')
516
+ @sock.should_receive(:write).with(expected)
517
+ @sphinx.SetRetries(10)
518
+ sphinx_safe_call { @sphinx.Query('query') }
519
+ end
520
+
521
+ it 'should generate valid request with retries count and delay specified' do
522
+ expected = sphinx_fixture('retries_delay')
523
+ @sock.should_receive(:write).with(expected)
524
+ @sphinx.SetRetries(10, 20)
525
+ sphinx_safe_call { @sphinx.Query('query') }
526
+ end
527
+
528
+ it 'should generate valid request for SetOverride' do
529
+ expected = sphinx_fixture('set_override')
530
+ @sock.should_receive(:write).with(expected)
531
+ @sphinx.SetOverride('attr1', Sphinx::Client::SPH_ATTR_INTEGER, { 10 => 20 })
532
+ @sphinx.SetOverride('attr2', Sphinx::Client::SPH_ATTR_FLOAT, { 11 => 30.3 })
533
+ @sphinx.SetOverride('attr3', Sphinx::Client::SPH_ATTR_BIGINT, { 12 => 1099511627780 })
534
+ sphinx_safe_call { @sphinx.Query('query') }
535
+ end
536
+
537
+ it 'should generate valid request for SetSelect' do
538
+ expected = sphinx_fixture('select')
539
+ @sock.should_receive(:write).with(expected)
540
+ @sphinx.SetSelect('attr1, attr2')
541
+ sphinx_safe_call { @sphinx.Query('query') }
542
+ end
543
+ end
544
+
545
+ context 'in RunQueries method' do
546
+ before(:each) do
547
+ @sphinx = sphinx_create_client
548
+ end
549
+
550
+ it 'should generate valid request for multiple queries' do
551
+ expected = sphinx_fixture('miltiple_queries')
552
+ @sock.should_receive(:write).with(expected)
553
+
554
+ @sphinx.SetRetries(10, 20)
555
+ @sphinx.AddQuery('test1')
556
+ @sphinx.SetGroupBy('attr', Sphinx::Client::SPH_GROUPBY_DAY)
557
+ @sphinx.AddQuery('test2')
558
+
559
+ sphinx_safe_call { @sphinx.RunQueries }
560
+ end
561
+ end
562
+
563
+ context 'in BuildExcerpts method' do
564
+ before :each do
565
+ @sphinx = sphinx_create_client
566
+ end
567
+
568
+ it 'should generate valid request with default parameters' do
569
+ expected = sphinx_fixture('excerpt_default')
570
+ @sock.should_receive(:write).with(expected)
571
+ sphinx_safe_call { @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2') }
572
+ end
573
+
574
+ it 'should generate valid request with custom parameters' do
575
+ expected = sphinx_fixture('excerpt_custom')
576
+ @sock.should_receive(:write).with(expected)
577
+ sphinx_safe_call do
578
+ @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2', { 'before_match' => 'before',
579
+ 'after_match' => 'after',
580
+ 'chunk_separator' => 'separator',
581
+ 'limit' => 10 })
582
+ end
583
+ end
584
+
585
+ it 'should generate valid request with custom parameters as symbols' do
586
+ expected = sphinx_fixture('excerpt_custom')
587
+ @sock.should_receive(:write).with(expected)
588
+ sphinx_safe_call do
589
+ @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2', { :before_match => 'before',
590
+ :after_match => 'after',
591
+ :chunk_separator => 'separator',
592
+ :limit => 10 })
593
+ end
594
+ end
595
+
596
+ it 'should generate valid request with flags' do
597
+ expected = sphinx_fixture('excerpt_flags')
598
+ @sock.should_receive(:write).with(expected)
599
+ sphinx_safe_call do
600
+ @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2', { 'exact_phrase' => true,
601
+ 'single_passage' => true,
602
+ 'use_boundaries' => true,
603
+ 'weight_order' => true,
604
+ 'query_mode' => true })
605
+ end
606
+ end
607
+
608
+ it 'should generate valid request with flags as symbols' do
609
+ expected = sphinx_fixture('excerpt_flags')
610
+ @sock.should_receive(:write).with(expected)
611
+ sphinx_safe_call do
612
+ @sphinx.BuildExcerpts(['10', '20'], 'index', 'word1 word2', { :exact_phrase => true,
613
+ :single_passage => true,
614
+ :use_boundaries => true,
615
+ :weight_order => true,
616
+ :query_mode => true })
617
+ end
618
+ end
619
+ end
620
+
621
+ context 'in BuildKeywords method' do
622
+ before :each do
623
+ @sphinx = sphinx_create_client
624
+ end
625
+
626
+ it 'should generate valid request' do
627
+ expected = sphinx_fixture('keywords')
628
+ @sock.should_receive(:write).with(expected)
629
+ sphinx_safe_call { @sphinx.BuildKeywords('test', 'index', true) }
630
+ end
631
+ end
632
+
633
+ context 'in UpdateAttributes method' do
634
+ before :each do
635
+ @sphinx = sphinx_create_client
636
+ end
637
+
638
+ it 'should generate valid request' do
639
+ expected = sphinx_fixture('update_attributes')
640
+ @sock.should_receive(:write).with(expected)
641
+ sphinx_safe_call { @sphinx.UpdateAttributes('index', ['group'], { 123 => [456] }) }
642
+ end
643
+
644
+ it 'should generate valid request for MVA' do
645
+ expected = sphinx_fixture('update_attributes_mva')
646
+ @sock.should_receive(:write).with(expected)
647
+ sphinx_safe_call { @sphinx.UpdateAttributes('index', ['group', 'category'], { 123 => [ [456, 789], [1, 2, 3] ] }, true) }
648
+ end
649
+ end
650
+ end