sphinx 0.9.10 → 0.9.10.2043

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