sphinx 0.9.10.2091 → 0.9.10.2094

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.
@@ -158,6 +158,27 @@ It means that you can chain filtering methods:
158
158
  set_id_range(10, 1000).
159
159
  query('test')
160
160
 
161
+ There is a handful ability to set query parameters directly in <tt>query</tt>
162
+ call. If block does not accept any parameters, it will be eval'ed inside
163
+ Sphinx::Client instance:
164
+
165
+ results = Sphinx::Client.new.query('test') do
166
+ match_mode :any
167
+ ranking_mode :bm25
168
+ id_range 10, 1000
169
+ end
170
+
171
+ As you can see, in this case you can omit the <tt>set_</tt> prefix for
172
+ this methods. If block accepts a parameter, sphinx instance will be
173
+ passed into the block. In this case you should you full method names
174
+ including the <tt>set_</tt> prefix:
175
+
176
+ results = Sphinx::Client.new.query('test') do |sphinx|
177
+ sphinx.set_match_mode :any
178
+ sphinx.set_ranking_mode :bm25
179
+ sphinx.set_id_range 10, 1000
180
+ end
181
+
161
182
  == Example
162
183
 
163
184
  This simple example illustrates base connection establishing,
@@ -2,4 +2,4 @@
2
2
  :major: 0
3
3
  :minor: 9
4
4
  :patch: 10
5
- :build: 2091
5
+ :build: 2094
@@ -11,10 +11,15 @@
11
11
  # You can freely distribute/modify this library.
12
12
  #
13
13
  module Sphinx
14
+ VERSION = begin
15
+ config = YAML.load(File.read(File.dirname(__FILE__) + '/../VERSION.yml'))
16
+ "#{config[:major]}.#{config[:minor]}.#{config[:patch]}.#{config[:build]}"
17
+ end
14
18
  end
15
19
 
16
- require 'socket'
17
20
  require 'net/protocol'
21
+ require 'socket'
22
+ require 'zlib'
18
23
 
19
24
  require File.dirname(__FILE__) + '/sphinx/request'
20
25
  require File.dirname(__FILE__) + '/sphinx/response'
@@ -120,6 +120,9 @@ module Sphinx
120
120
  # Number of request retries.
121
121
  # @private
122
122
  attr_reader :reqretries
123
+ # Log debug/info/warn/error to the given Logger, defaults to nil.
124
+ # @private
125
+ attr_reader :logger
123
126
 
124
127
  #=================================================================
125
128
  # Known match modes
@@ -229,7 +232,7 @@ module Sphinx
229
232
  SPH_GROUPBY_ATTRPAIR = 5
230
233
 
231
234
  # Constructs the <tt>Sphinx::Client</tt> object and sets options to their default values.
232
- def initialize
235
+ def initialize(logger = nil)
233
236
  # per-query settings
234
237
  @offset = 0 # how many records to seek from result-set start (default is 0)
235
238
  @limit = 20 # how many records to return from result-set starting at offset (default is 20)
@@ -271,7 +274,41 @@ module Sphinx
271
274
  # per-client-object settings
272
275
  # searchd servers list
273
276
  @servers = [Sphinx::Server.new(self, 'localhost', 9312, false)].freeze
274
- @lastserver = -1
277
+ @logger = logger
278
+
279
+ logger.info { "[sphinx] version: #{VERSION}, #{@servers.inspect}" } if logger
280
+ end
281
+
282
+ # Returns a string representation of the sphinx client object.
283
+ #
284
+ def inspect
285
+ params = {
286
+ :error => @error,
287
+ :warning => @warning,
288
+ :connect_error => @connerror,
289
+ :servers => @servers,
290
+ :connect_timeout => { :timeout => @timeout, :retries => @retries },
291
+ :request_timeout => { :timeout => @reqtimeout, :retries => @reqretries },
292
+ :retries => { :count => @retrycount, :delay => @retrydelay },
293
+ :limits => { :offset => @offset, :limit => @limit, :max => @maxmatches, :cutoff => @cutoff },
294
+ :max_query_time => @maxquerytime,
295
+ :overrides => @overrides,
296
+ :select => @select,
297
+ :match_mode => @mode,
298
+ :ranking_mode => @ranker,
299
+ :sort_mode => { :mode => @sort, :sortby => @sortby },
300
+ :weights => @weights,
301
+ :field_weights => @fieldweights,
302
+ :index_weights => @indexweights,
303
+ :id_range => { :min => @min_id, :max => @max_id },
304
+ :filters => @filters,
305
+ :geo_anchor => @anchor,
306
+ :group_by => { :attribute => @groupby, :func => @groupfunc, :sort => @groupsort },
307
+ :group_distinct => @groupdistinct
308
+ }
309
+
310
+ "<Sphinx::Client: %d servers, params: %s>" %
311
+ [@servers.length, params.inspect]
275
312
  end
276
313
 
277
314
  #=================================================================
@@ -375,12 +412,13 @@ module Sphinx
375
412
  elsif host[0, 7] == 'unix://'
376
413
  path = host[7..-1]
377
414
  else
378
- raise ArgumentError, '"port" argument must be Integer' unless port.respond_to?(:integer?) and port.integer?
415
+ raise ArgumentError, '"port" argument must be Integer' unless port.kind_of?(Integer)
379
416
  end
380
417
 
381
418
  host = port = nil unless path.nil?
382
419
 
383
420
  @servers = [Sphinx::Server.new(self, host, port, path)].freeze
421
+ logger.info { "[sphinx] servers now: #{@servers.inspect}" } if logger
384
422
  self
385
423
  end
386
424
  alias :SetServer :set_server
@@ -431,13 +469,14 @@ module Sphinx
431
469
  elsif host[0, 7] == 'unix://'
432
470
  path = host[7..-1]
433
471
  else
434
- raise ArgumentError, '"port" argument must be Integer' unless port.respond_to?(:integer?) and port.integer?
472
+ raise ArgumentError, '"port" argument must be Integer' unless port.kind_of?(Integer)
435
473
  end
436
474
 
437
475
  host = port = nil unless path.nil?
438
476
 
439
477
  Sphinx::Server.new(self, host, port, path)
440
478
  end.freeze
479
+ logger.info { "[sphinx] servers now: #{@servers.inspect}" } if logger
441
480
  self
442
481
  end
443
482
  alias :SetServers :set_servers
@@ -470,8 +509,8 @@ module Sphinx
470
509
  # @see #set_request_timeout
471
510
  #
472
511
  def set_connect_timeout(timeout, retries = 1)
473
- raise ArgumentError, '"timeout" argument must be Integer' unless timeout.respond_to?(:integer?) and timeout.integer?
474
- raise ArgumentError, '"retries" argument must be Integer' unless retries.respond_to?(:integer?) and retries.integer?
512
+ raise ArgumentError, '"timeout" argument must be Integer' unless timeout.kind_of?(Integer)
513
+ raise ArgumentError, '"retries" argument must be Integer' unless retries.kind_of?(Integer)
475
514
  raise ArgumentError, '"retries" argument must be greater than 0' unless retries > 0
476
515
 
477
516
  @timeout = timeout
@@ -508,8 +547,8 @@ module Sphinx
508
547
  # @see #set_connect_timeout
509
548
  #
510
549
  def set_request_timeout(timeout, retries = 1)
511
- raise ArgumentError, '"timeout" argument must be Integer' unless timeout.respond_to?(:integer?) and timeout.integer?
512
- raise ArgumentError, '"retries" argument must be Integer' unless retries.respond_to?(:integer?) and retries.integer?
550
+ raise ArgumentError, '"timeout" argument must be Integer' unless timeout.kind_of?(Integer)
551
+ raise ArgumentError, '"retries" argument must be Integer' unless retries.kind_of?(Integer)
513
552
  raise ArgumentError, '"retries" argument must be greater than 0' unless retries > 0
514
553
 
515
554
  @reqtimeout = timeout
@@ -539,8 +578,8 @@ module Sphinx
539
578
  # @see #set_request_timeout
540
579
  #
541
580
  def set_retries(count, delay = 0)
542
- raise ArgumentError, '"count" argument must be Integer' unless count.respond_to?(:integer?) and count.integer?
543
- raise ArgumentError, '"delay" argument must be Integer' unless delay.respond_to?(:integer?) and delay.integer?
581
+ raise ArgumentError, '"count" argument must be Integer' unless count.kind_of?(Integer)
582
+ raise ArgumentError, '"delay" argument must be Integer' unless delay.kind_of?(Integer)
544
583
 
545
584
  @retrycount = count
546
585
  @retrydelay = delay
@@ -596,10 +635,10 @@ module Sphinx
596
635
  # @raise [ArgumentError] Occurred when parameters are invalid.
597
636
  #
598
637
  def set_limits(offset, limit, max = 0, cutoff = 0)
599
- raise ArgumentError, '"offset" argument must be Integer' unless offset.respond_to?(:integer?) and offset.integer?
600
- raise ArgumentError, '"limit" argument must be Integer' unless limit.respond_to?(:integer?) and limit.integer?
601
- raise ArgumentError, '"max" argument must be Integer' unless max.respond_to?(:integer?) and max.integer?
602
- raise ArgumentError, '"cutoff" argument must be Integer' unless cutoff.respond_to?(:integer?) and cutoff.integer?
638
+ raise ArgumentError, '"offset" argument must be Integer' unless offset.kind_of?(Integer)
639
+ raise ArgumentError, '"limit" argument must be Integer' unless limit.kind_of?(Integer)
640
+ raise ArgumentError, '"max" argument must be Integer' unless max.kind_of?(Integer)
641
+ raise ArgumentError, '"cutoff" argument must be Integer' unless cutoff.kind_of?(Integer)
603
642
 
604
643
  raise ArgumentError, '"offset" argument should be greater or equal to zero' unless offset >= 0
605
644
  raise ArgumentError, '"limit" argument should be greater to zero' unless limit > 0
@@ -632,7 +671,7 @@ module Sphinx
632
671
  # @raise [ArgumentError] Occurred when parameters are invalid.
633
672
  #
634
673
  def set_max_query_time(max)
635
- raise ArgumentError, '"max" argument must be Integer' unless max.respond_to?(:integer?) and max.integer?
674
+ raise ArgumentError, '"max" argument must be Integer' unless max.kind_of?(Integer)
636
675
  raise ArgumentError, '"max" argument should be greater or equal to zero' unless max >= 0
637
676
 
638
677
  @maxquerytime = max
@@ -691,15 +730,15 @@ module Sphinx
691
730
  raise ArgumentError, '"values" argument must be Hash' unless values.kind_of?(Hash)
692
731
 
693
732
  values.each do |id, value|
694
- raise ArgumentError, '"values" argument must be Hash map of Integer to Integer or Time' unless id.respond_to?(:integer?) and id.integer?
733
+ raise ArgumentError, '"values" argument must be Hash map of Integer to Integer or Time' unless id.kind_of?(Integer)
695
734
  case attrtype
696
735
  when SPH_ATTR_TIMESTAMP
697
- raise ArgumentError, '"values" argument must be Hash map of Integer to Integer or Time' unless (value.respond_to?(:integer?) and value.integer?) or value.kind_of?(Time)
736
+ raise ArgumentError, '"values" argument must be Hash map of Integer to Numeric' unless value.kind_of?(Integer) or value.kind_of?(Time)
698
737
  when SPH_ATTR_FLOAT
699
- raise ArgumentError, '"values" argument must be Hash map of Integer to Float or Integer' unless value.kind_of?(Float) or (value.respond_to?(:integer?) and value.integer?)
738
+ raise ArgumentError, '"values" argument must be Hash map of Integer to Numeric' unless value.kind_of?(Numeric)
700
739
  else
701
740
  # SPH_ATTR_INTEGER, SPH_ATTR_ORDINAL, SPH_ATTR_BOOL, SPH_ATTR_BIGINT
702
- raise ArgumentError, '"values" argument must be Hash map of Integer to Integer' unless value.respond_to?(:integer?) and value.integer?
741
+ raise ArgumentError, '"values" argument must be Hash map of Integer to Integer' unless value.kind_of?(Integer)
703
742
  end
704
743
  end
705
744
 
@@ -900,7 +939,7 @@ module Sphinx
900
939
  def set_weights(weights)
901
940
  raise ArgumentError, '"weights" argument must be Array' unless weights.kind_of?(Array)
902
941
  weights.each do |weight|
903
- raise ArgumentError, '"weights" argument must be Array of integers' unless weight.respond_to?(:integer?) and weight.integer?
942
+ raise ArgumentError, '"weights" argument must be Array of integers' unless weight.kind_of?(Integer)
904
943
  end
905
944
 
906
945
  @weights = weights
@@ -945,7 +984,7 @@ module Sphinx
945
984
  def set_field_weights(weights)
946
985
  raise ArgumentError, '"weights" argument must be Hash' unless weights.kind_of?(Hash)
947
986
  weights.each do |name, weight|
948
- unless (name.kind_of?(String) or name.kind_of?(Symbol)) and (weight.respond_to?(:integer?) and weight.integer?)
987
+ unless (name.kind_of?(String) or name.kind_of?(Symbol)) and weight.kind_of?(Integer)
949
988
  raise ArgumentError, '"weights" argument must be Hash map of strings to integers'
950
989
  end
951
990
  end
@@ -991,7 +1030,7 @@ module Sphinx
991
1030
  def set_index_weights(weights)
992
1031
  raise ArgumentError, '"weights" argument must be Hash' unless weights.kind_of?(Hash)
993
1032
  weights.each do |index, weight|
994
- unless (index.kind_of?(String) or index.kind_of?(Symbol)) and (weight.respond_to?(:integer?) and weight.integer?)
1033
+ unless (index.kind_of?(String) or index.kind_of?(Symbol)) and weight.kind_of?(Integer)
995
1034
  raise ArgumentError, '"weights" argument must be Hash map of strings to integers'
996
1035
  end
997
1036
  end
@@ -1000,7 +1039,7 @@ module Sphinx
1000
1039
  self
1001
1040
  end
1002
1041
  alias :SetIndexWeights :set_index_weights
1003
-
1042
+
1004
1043
  #=================================================================
1005
1044
  # Result set filtering settings
1006
1045
  #=================================================================
@@ -1024,8 +1063,8 @@ module Sphinx
1024
1063
  # @see http://www.sphinxsearch.com/docs/current.html#api-func-setidrange Section 6.4.1, "SetIDRange"
1025
1064
  #
1026
1065
  def set_id_range(min, max)
1027
- raise ArgumentError, '"min" argument must be Integer' unless min.respond_to?(:integer?) and min.integer?
1028
- raise ArgumentError, '"max" argument must be Integer' unless max.respond_to?(:integer?) and max.integer?
1066
+ raise ArgumentError, '"min" argument must be Integer' unless min.kind_of?(Integer)
1067
+ raise ArgumentError, '"max" argument must be Integer' unless max.kind_of?(Integer)
1029
1068
  raise ArgumentError, '"max" argument greater or equal to "min"' unless min <= max
1030
1069
 
1031
1070
  @min_id = min
@@ -1070,7 +1109,7 @@ module Sphinx
1070
1109
  raise ArgumentError, '"exclude" argument must be Boolean' unless exclude.kind_of?(TrueClass) or exclude.kind_of?(FalseClass)
1071
1110
 
1072
1111
  values.each do |value|
1073
- raise ArgumentError, '"values" argument must be Array of Integer' unless value.respond_to?(:integer?) and value.integer?
1112
+ raise ArgumentError, '"values" argument must be Array of Integer' unless value.kind_of?(Integer)
1074
1113
  end
1075
1114
 
1076
1115
  @filters << { 'type' => SPH_FILTER_VALUES, 'attr' => attribute.to_s, 'exclude' => exclude, 'values' => values }
@@ -1112,8 +1151,8 @@ module Sphinx
1112
1151
  #
1113
1152
  def set_filter_range(attribute, min, max, exclude = false)
1114
1153
  raise ArgumentError, '"attribute" argument must be String or Symbol' unless attribute.kind_of?(String) or attribute.kind_of?(Symbol)
1115
- raise ArgumentError, '"min" argument must be Integer' unless min.respond_to?(:integer?) and min.integer?
1116
- raise ArgumentError, '"max" argument must be Integer' unless max.respond_to?(:integer?) and max.integer?
1154
+ raise ArgumentError, '"min" argument must be Integer' unless min.kind_of?(Integer)
1155
+ raise ArgumentError, '"max" argument must be Integer' unless max.kind_of?(Integer)
1117
1156
  raise ArgumentError, '"max" argument greater or equal to "min"' unless min <= max
1118
1157
  raise ArgumentError, '"exclude" argument must be Boolean' unless exclude.kind_of?(TrueClass) or exclude.kind_of?(FalseClass)
1119
1158
 
@@ -1137,8 +1176,8 @@ module Sphinx
1137
1176
  # if +exclude+ is true).
1138
1177
  #
1139
1178
  # @param [String, Symbol] attribute an attribute name to filter by.
1140
- # @param [Integer, Float] min min value of the given attribute.
1141
- # @param [Integer, Float] max max value of the given attribute.
1179
+ # @param [Numeric] min min value of the given attribute.
1180
+ # @param [Numeric] max max value of the given attribute.
1142
1181
  # @param [Boolean] exclude indicating whether documents with given attribute
1143
1182
  # matching specified boundaries should be excluded from search results.
1144
1183
  # @return [Sphinx::Client] self.
@@ -1155,8 +1194,8 @@ module Sphinx
1155
1194
  #
1156
1195
  def set_filter_float_range(attribute, min, max, exclude = false)
1157
1196
  raise ArgumentError, '"attribute" argument must be String or Symbol' unless attribute.kind_of?(String) or attribute.kind_of?(Symbol)
1158
- raise ArgumentError, '"min" argument must be Float or Integer' unless min.kind_of?(Float) or (min.respond_to?(:integer?) and min.integer?)
1159
- raise ArgumentError, '"max" argument must be Float or Integer' unless max.kind_of?(Float) or (max.respond_to?(:integer?) and max.integer?)
1197
+ raise ArgumentError, '"min" argument must be Numeric' unless min.kind_of?(Numeric)
1198
+ raise ArgumentError, '"max" argument must be Numeric' unless max.kind_of?(Numeric)
1160
1199
  raise ArgumentError, '"max" argument greater or equal to "min"' unless min <= max
1161
1200
  raise ArgumentError, '"exclude" argument must be Boolean' unless exclude.kind_of?(TrueClass) or exclude.kind_of?(FalseClass)
1162
1201
 
@@ -1185,8 +1224,8 @@ module Sphinx
1185
1224
  #
1186
1225
  # @param [String, Symbol] attrlat a name of latitude attribute.
1187
1226
  # @param [String, Symbol] attrlong a name of longitude attribute.
1188
- # @param [Integer, Float] lat an anchor point latitude, in radians.
1189
- # @param [Integer, Float] long an anchor point longitude, in radians.
1227
+ # @param [Numeric] lat an anchor point latitude, in radians.
1228
+ # @param [Numeric] long an anchor point longitude, in radians.
1190
1229
  # @return [Sphinx::Client] self.
1191
1230
  #
1192
1231
  # @example
@@ -1199,14 +1238,14 @@ module Sphinx
1199
1238
  def set_geo_anchor(attrlat, attrlong, lat, long)
1200
1239
  raise ArgumentError, '"attrlat" argument must be String or Symbol' unless attrlat.kind_of?(String) or attrlat.kind_of?(Symbol)
1201
1240
  raise ArgumentError, '"attrlong" argument must be String or Symbol' unless attrlong.kind_of?(String) or attrlong.kind_of?(Symbol)
1202
- raise ArgumentError, '"lat" argument must be Float or Integer' unless lat.kind_of?(Float) or (lat.respond_to?(:integer?) and lat.integer?)
1203
- raise ArgumentError, '"long" argument must be Float or Integer' unless long.kind_of?(Float) or (long.respond_to?(:integer?) and long.integer?)
1241
+ raise ArgumentError, '"lat" argument must be Numeric' unless lat.kind_of?(Numeric)
1242
+ raise ArgumentError, '"long" argument must be Numeric' unless long.kind_of?(Numeric)
1204
1243
 
1205
1244
  @anchor = { 'attrlat' => attrlat.to_s, 'attrlong' => attrlong.to_s, 'lat' => lat.to_f, 'long' => long.to_f }
1206
1245
  self
1207
1246
  end
1208
1247
  alias :SetGeoAnchor :set_geo_anchor
1209
-
1248
+
1210
1249
  #=================================================================
1211
1250
  # GROUP BY settings
1212
1251
  #=================================================================
@@ -1473,18 +1512,48 @@ module Sphinx
1473
1512
  # @param [String] index an index name (or names).
1474
1513
  # @param [String] comment a comment to be sent to the query log.
1475
1514
  # @return [Hash, false] result set described above or +false+ on error.
1515
+ # @yield [Client] yields just before query performing. Useful to set
1516
+ # filters or sortings. When block does not accept any parameters, it
1517
+ # will be eval'ed inside {Client} instance itself. In this case you
1518
+ # can omit +set_+ prefix for configuration methods.
1519
+ # @yieldparam [Client] sphinx self.
1476
1520
  #
1477
- # @example
1521
+ # @example Regular query with previously set filters
1478
1522
  # sphinx.query('some search text', '*', 'search page')
1523
+ # @example Query with block
1524
+ # sphinx.query('test') do |sphinx|
1525
+ # sphinx.set_match_mode :all
1526
+ # sphinx.set_id_range 10, 100
1527
+ # end
1528
+ # @example Query with instant filters configuring
1529
+ # sphinx.query('test') do
1530
+ # match_mode :all
1531
+ # id_range 10, 100
1532
+ # end
1479
1533
  #
1480
1534
  # @see http://www.sphinxsearch.com/docs/current.html#api-func-query Section 6.6.1, "Query"
1481
1535
  # @see #add_query
1482
1536
  # @see #run_queries
1483
1537
  #
1484
- def query(query, index = '*', comment = '')
1538
+ def query(query, index = '*', comment = '', &block)
1485
1539
  @reqs = []
1486
1540
 
1487
- self.add_query(query, index, comment)
1541
+ if block_given?
1542
+ if block.arity > 0
1543
+ yield self
1544
+ else
1545
+ begin
1546
+ @inside_eval = true
1547
+ instance_eval(&block)
1548
+ ensure
1549
+ @inside_eval = false
1550
+ end
1551
+ end
1552
+ end
1553
+
1554
+ logger.debug { "[sphinx] query('#{query}', '#{index}', '#{comment}'), #{self.inspect}" } if logger
1555
+
1556
+ self.add_query(query, index, comment, false)
1488
1557
  results = self.run_queries
1489
1558
 
1490
1559
  # probably network error; error message should be already filled
@@ -1512,12 +1581,12 @@ module Sphinx
1512
1581
  # cases. They do not result in any additional overheads compared
1513
1582
  # to simple queries. Thus, if you run several different queries
1514
1583
  # from your web page, you should always consider using multi-queries.
1515
- #
1584
+ #
1516
1585
  # For instance, running the same full-text query but with different
1517
1586
  # sorting or group-by settings will enable searchd to perform
1518
1587
  # expensive full-text search and ranking operation only once, but
1519
1588
  # compute multiple group-by results from its output.
1520
- #
1589
+ #
1521
1590
  # This can be a big saver when you need to display not just plain
1522
1591
  # search results but also some per-category counts, such as the
1523
1592
  # amount of products grouped by vendor. Without multi-query, you
@@ -1526,21 +1595,21 @@ module Sphinx
1526
1595
  # sets differently. With multi-query, you simply pass all these
1527
1596
  # queries in a single batch and Sphinx optimizes the redundant
1528
1597
  # full-text search internally.
1529
- #
1598
+ #
1530
1599
  # {#add_query} internally saves full current settings state along
1531
1600
  # with the query, and you can safely change them afterwards for
1532
1601
  # subsequent {#add_query} calls. Already added queries will not
1533
1602
  # be affected; there's actually no way to change them at all.
1534
1603
  # Here's an example:
1535
- #
1604
+ #
1536
1605
  # sphinx.set_sort_mode(:relevance)
1537
1606
  # sphinx.add_query("hello world", "documents")
1538
- #
1607
+ #
1539
1608
  # sphinx.set_sort_mode(:attr_desc, :price)
1540
1609
  # sphinx.add_query("ipod", "products")
1541
1610
  #
1542
1611
  # sphinx.add_query("harry potter", "books")
1543
- #
1612
+ #
1544
1613
  # results = sphinx.run_queries
1545
1614
  #
1546
1615
  # With the code above, 1st query will search for "hello world"
@@ -1550,18 +1619,18 @@ module Sphinx
1550
1619
  # "books" index while still sorting by price. Note that 2nd
1551
1620
  # {#set_sort_mode} call does not affect the first query (because
1552
1621
  # it's already added) but affects both other subsequent queries.
1553
- #
1622
+ #
1554
1623
  # Additionally, any filters set up before an {#add_query} will
1555
1624
  # fall through to subsequent queries. So, if {#set_filter} is
1556
1625
  # called before the first query, the same filter will be in
1557
1626
  # place for the second (and subsequent) queries batched through
1558
1627
  # {#add_query} unless you call {#reset_filters} first. Alternatively,
1559
1628
  # you can add additional filters as well.
1560
- #
1629
+ #
1561
1630
  # This would also be true for grouping options and sorting options;
1562
1631
  # no current sorting, filtering, and grouping settings are affected
1563
1632
  # by this call; so subsequent queries will reuse current query settings.
1564
- #
1633
+ #
1565
1634
  # {#add_query} returns an index into an array of results that will
1566
1635
  # be returned from {#run_queries} call. It is simply a sequentially
1567
1636
  # increasing 0-based integer, ie. first call will return 0, second
@@ -1571,6 +1640,7 @@ module Sphinx
1571
1640
  # @param [String] query a query string.
1572
1641
  # @param [String] index an index name (or names).
1573
1642
  # @param [String] comment a comment to be sent to the query log.
1643
+ # @param [Boolean] log indicating whether this call should be logged.
1574
1644
  # @return [Integer] an index into an array of results that will
1575
1645
  # be returned from {#run_queries} call.
1576
1646
  #
@@ -1581,7 +1651,8 @@ module Sphinx
1581
1651
  # @see #query
1582
1652
  # @see #run_queries
1583
1653
  #
1584
- def add_query(query, index = '*', comment = '')
1654
+ def add_query(query, index = '*', comment = '', log = true)
1655
+ logger.debug { "[sphinx] add_query('#{query}', '#{index}', '#{comment}'), #{self.inspect}" } if log and logger
1585
1656
  # build request
1586
1657
 
1587
1658
  # mode and limits
@@ -1716,6 +1787,7 @@ module Sphinx
1716
1787
  # @see #add_query
1717
1788
  #
1718
1789
  def run_queries
1790
+ logger.debug { "[sphinx] run_queries(#{@reqs.length} queries)" } if logger
1719
1791
  if @reqs.empty?
1720
1792
  @error = 'No queries defined, issue add_query() first'
1721
1793
  return false
@@ -1767,7 +1839,7 @@ module Sphinx
1767
1839
  end
1768
1840
 
1769
1841
  # This is a single result put in the result['matches'] array
1770
- match = { 'id' => doc, 'weight' => weight }
1842
+ match = { 'id' => doc, 'weight' => weight }
1771
1843
  match['attrs'] = attrs_names_in_order.inject({}) do |hash, name|
1772
1844
  hash[name] = case attrs[name]
1773
1845
  when SPH_ATTR_BIGINT
@@ -1806,7 +1878,7 @@ module Sphinx
1806
1878
  end
1807
1879
  end
1808
1880
  alias :RunQueries :run_queries
1809
-
1881
+
1810
1882
  #=================================================================
1811
1883
  # Additional functionality
1812
1884
  #=================================================================
@@ -1983,18 +2055,18 @@ module Sphinx
1983
2055
  # Instantly updates given attribute values in given documents.
1984
2056
  # Returns number of actually updated documents (0 or more) on
1985
2057
  # success, or -1 on failure.
1986
- #
2058
+ #
1987
2059
  # +index+ is a name of the index (or indexes) to be updated.
1988
2060
  # +attrs+ is a plain array with string attribute names, listing
1989
2061
  # attributes that are updated. +values+ is a Hash where key is
1990
2062
  # document ID, and value is a plain array of new attribute values.
1991
- #
2063
+ #
1992
2064
  # +index+ can be either a single index name or a list, like in
1993
2065
  # {#query}. Unlike {#query}, wildcard is not allowed and all the
1994
2066
  # indexes to update must be specified explicitly. The list of
1995
2067
  # indexes can include distributed index names. Updates on
1996
2068
  # distributed indexes will be pushed to all agents.
1997
- #
2069
+ #
1998
2070
  # The updates only work with docinfo=extern storage strategy.
1999
2071
  # They are very fast because they're working fully in RAM, but
2000
2072
  # they can also be made persistent: updates are saved on disk
@@ -2009,7 +2081,7 @@ module Sphinx
2009
2081
  # stock to 5; for document 1002, the new price will be 37 and the
2010
2082
  # new amount will be 11; etc. The third one updates document 1
2011
2083
  # in index "test2", setting MVA attribute "group_id" to [456, 789].
2012
- #
2084
+ #
2013
2085
  # @example
2014
2086
  # sphinx.update_attributes("test1", ["group_id"], { 1 => [456] });
2015
2087
  # sphinx.update_attributes("products", ["price", "amount_in_stock"],
@@ -2040,17 +2112,17 @@ module Sphinx
2040
2112
 
2041
2113
  raise ArgumentError, '"values" argument must be Hash' unless values.kind_of?(Hash)
2042
2114
  values.each do |id, entry|
2043
- raise ArgumentError, '"values" argument must be Hash map of Integer to Array' unless id.respond_to?(:integer?) and id.integer?
2115
+ raise ArgumentError, '"values" argument must be Hash map of Integer to Array' unless id.kind_of?(Integer)
2044
2116
  raise ArgumentError, '"values" argument must be Hash map of Integer to Array' unless entry.kind_of?(Array)
2045
2117
  raise ArgumentError, "\"values\" argument Hash values Array must have #{attrs.length} elements" unless entry.length == attrs.length
2046
2118
  entry.each do |v|
2047
2119
  if mva
2048
2120
  raise ArgumentError, '"values" argument must be Hash map of Integer to Array of Arrays' unless v.kind_of?(Array)
2049
2121
  v.each do |vv|
2050
- raise ArgumentError, '"values" argument must be Hash map of Integer to Array of Arrays of Integers' unless vv.respond_to?(:integer?) and vv.integer?
2122
+ raise ArgumentError, '"values" argument must be Hash map of Integer to Array of Arrays of Integers' unless vv.kind_of?(Integer)
2051
2123
  end
2052
2124
  else
2053
- raise ArgumentError, '"values" argument must be Hash map of Integer to Array of Integers' unless v.respond_to?(:integer?) and v.integer?
2125
+ raise ArgumentError, '"values" argument must be Hash map of Integer to Array of Integers' unless v.kind_of?(Integer)
2054
2126
  end
2055
2127
  end
2056
2128
  end
@@ -2085,22 +2157,52 @@ module Sphinx
2085
2157
  # Queries searchd status, and returns an array of status variable name
2086
2158
  # and value pairs.
2087
2159
  #
2088
- # @return [Array<Array>] a table containing searchd status information.
2160
+ # @return [Array<Array>, Array<Hash>] a table containing searchd status information.
2161
+ # If there are more than one server configured ({#set_servers}), an
2162
+ # +Array+ of +Hash+es will be returned, one for each server. Hash will
2163
+ # contain <tt>:server</tt> element with string name of server (<tt>host:port</tt>)
2164
+ # and <tt>:status</tt> table just like one for a single server. In case of
2165
+ # any error, it will be stored in the <tt>:error</tt> key.
2089
2166
  #
2090
- # @example
2167
+ # @example Single server
2091
2168
  # status = sphinx.status
2092
2169
  # puts status.map { |key, value| "#{key.rjust(20)}: #{value}" }
2093
2170
  #
2171
+ # @example Multiple servers
2172
+ # sphinx.set_servers([
2173
+ # { :host => 'localhost' },
2174
+ # { :host => 'browse02.local' }
2175
+ # ])
2176
+ # sphinx.status.each do |report|
2177
+ # puts "=== #{report[:server]}"
2178
+ # if report[:error]
2179
+ # puts "Error: #{report[:error]}"
2180
+ # else
2181
+ # puts report[:status].map { |key, value| "#{key.rjust(20)}: #{value}" }
2182
+ # end
2183
+ # end
2184
+ #
2094
2185
  def status
2095
2186
  request = Request.new
2096
2187
  request.put_int(1)
2097
- response = perform_request(:status, request)
2098
2188
 
2099
2189
  # parse response
2100
- rows, cols = response.get_ints(2)
2101
- (0...rows).map do
2102
- (0...cols).map { response.get_string }
2190
+ results = @servers.map do |server|
2191
+ begin
2192
+ response = perform_request(:status, request, nil, server)
2193
+ rows, cols = response.get_ints(2)
2194
+ status = (0...rows).map do
2195
+ (0...cols).map { response.get_string }
2196
+ end
2197
+ { :server => server.to_s, :status => status }
2198
+ rescue SphinxError
2199
+ # Re-raise error when a single server configured
2200
+ raise if @servers.size == 1
2201
+ { :server => server.to_s, :error => self.last_error}
2202
+ end
2103
2203
  end
2204
+
2205
+ @servers.size > 1 ? results : results.first[:status]
2104
2206
  end
2105
2207
  alias :Status :status
2106
2208
 
@@ -2212,7 +2314,11 @@ module Sphinx
2212
2314
  # <tt>:update</tt>, <tt>:keywords</tt>, <tt>:persist</tt>, <tt>:status</tt>,
2213
2315
  # <tt>:query</tt>, <tt>:flushattrs</tt>. See <tt>SEARCHD_COMMAND_*</tt> for details).
2214
2316
  # @param [Sphinx::Request] request contains request body.
2215
- # @param [nil, Integer] additional additional integer data to be placed between header and body.
2317
+ # @param [Integer] additional additional integer data to be placed between header and body.
2318
+ # @param [Sphinx::Server] server where perform request on. This is special
2319
+ # parameter for internal usage. If specified, request will be performed
2320
+ # on specified server, and it will try to establish connection to this
2321
+ # server only once.
2216
2322
  #
2217
2323
  # @yield if block given, response will not be parsed, plain socket
2218
2324
  # will be yielded instead. This is special mode used for
@@ -2223,8 +2329,24 @@ module Sphinx
2223
2329
  #
2224
2330
  # @see #parse_response
2225
2331
  #
2226
- def perform_request(command, request, additional = nil)
2227
- with_server do |server|
2332
+ def perform_request(command, request, additional = nil, server = nil)
2333
+ if server
2334
+ attempts = 1
2335
+ else
2336
+ server = case request
2337
+ when String
2338
+ Zlib.crc32(request)
2339
+ when Request
2340
+ request.crc32
2341
+ else
2342
+ raise ArgumentError, "request argument must be String or Sphinx::Request"
2343
+ end
2344
+ attempts = nil
2345
+ end
2346
+
2347
+ with_server(server, attempts) do |server|
2348
+ logger.info { "[sphinx] #{command} on server #{server}" } if logger
2349
+
2228
2350
  cmd = command.to_s.upcase
2229
2351
  command_id = Sphinx::Client.const_get("SEARCHD_COMMAND_#{cmd}")
2230
2352
  command_ver = Sphinx::Client.const_get("VER_COMMAND_#{cmd}")
@@ -2325,22 +2447,56 @@ module Sphinx
2325
2447
  # method will return true. Also, {SphinxConnectError} exception
2326
2448
  # will be raised.
2327
2449
  #
2450
+ # @overload with_server(server_index)
2451
+ # Get the server based on some seed value (usually CRC32 of
2452
+ # request. In this case initial server will be choosed using
2453
+ # this seed value, in case of connetion failure next server
2454
+ # in servers list will be used).
2455
+ # @param [Integer] server_index server index, must be any
2456
+ # integer value (not necessarily less than number of servers.)
2457
+ # @param [Integer] attempts how many retries to perform. Use
2458
+ # +nil+ to perform retries configured with {#set_connect_timeout}.
2459
+ # @overload with_server(server)
2460
+ # Get the server specified as a parameter. If specified, request
2461
+ # will be performed on specified server, and it will try to
2462
+ # establish connection to this server only once.
2463
+ # @param [Server] server server to perform request on.
2464
+ # @param [Integer] attempts how many retries to perform. Use
2465
+ # +nil+ to perform retries configured with {#set_connect_timeout}.
2466
+ #
2328
2467
  # @yield a block which performs request on a given server.
2329
2468
  # @yieldparam [Sphinx::Server] server contains information
2330
2469
  # about the server to perform request on.
2331
2470
  # @raise [SphinxConnectError] on any connection error.
2332
2471
  #
2333
- def with_server
2334
- attempts = @retries
2472
+ def with_server(server = nil, attempts = nil)
2473
+ case server
2474
+ when Server
2475
+ idx = @servers.index(server) || 0
2476
+ s = server
2477
+ when Integer
2478
+ idx = server % @servers.size
2479
+ s = @servers[idx]
2480
+ when NilClass
2481
+ idx = 0
2482
+ s = @servers[idx]
2483
+ else
2484
+ raise ArgumentError, 'server argument must be Integer or Sphinx::Server'
2485
+ end
2486
+ attempts ||= @retries
2335
2487
  begin
2336
- # Get the next server
2337
- @lastserver = (@lastserver + 1) % @servers.size
2338
- server = @servers[@lastserver]
2339
- yield server
2488
+ yield s
2340
2489
  rescue SphinxConnectError => e
2490
+ logger.warn { "[sphinx] server failed: #{e.class.name}: #{e.message}" } if logger
2341
2491
  # Connection error! Do we need to try it again?
2342
2492
  attempts -= 1
2343
- retry if attempts > 0
2493
+ if attempts > 0
2494
+ logger.info { "[sphinx] connection to server #{s.inspect} DIED! Retrying operation..." } if logger
2495
+ # Get the next server
2496
+ idx = (idx + 1) % @servers.size
2497
+ s = @servers[idx]
2498
+ retry
2499
+ end
2344
2500
 
2345
2501
  # Re-raise original exception
2346
2502
  @error = e.message
@@ -2407,6 +2563,7 @@ module Sphinx
2407
2563
  yield s
2408
2564
  end
2409
2565
  rescue SocketError, SystemCallError, IOError, ::Errno::EPIPE => e
2566
+ logger.warn { "[sphinx] socket failure: #{e.message}" } if logger
2410
2567
  # Ouch, communication problem, will be treated as a connection problem.
2411
2568
  raise SphinxConnectError, "failed to read searchd response (msg=#{e.message})"
2412
2569
  rescue SphinxResponseError, SphinxInternalError, SphinxTemporaryError, SphinxUnknownError, ::Timeout::Error, EOFError => e
@@ -2418,6 +2575,7 @@ module Sphinx
2418
2575
  new_e.set_backtrace(e.backtrace)
2419
2576
  e = new_e
2420
2577
  end
2578
+ logger.warn { "[sphinx] generic failure: #{e.class.name}: #{e.message}" } if logger
2421
2579
 
2422
2580
  # Close previously opened socket (in case of it has been really opened)
2423
2581
  server.free_socket(socket)
@@ -2434,5 +2592,21 @@ module Sphinx
2434
2592
  server.free_socket(socket)
2435
2593
  end
2436
2594
  end
2595
+
2596
+ # Enables ability to skip +set_+ prefix for methods inside {#query} block.
2597
+ #
2598
+ # @example
2599
+ # sphinx.query('test') do
2600
+ # match_mode :all
2601
+ # id_range 10, 100
2602
+ # end
2603
+ #
2604
+ def method_missing(method_id, *arguments, &block)
2605
+ if @inside_eval and self.respond_to?("set_#{method_id}")
2606
+ self.send("set_#{method_id}", *arguments)
2607
+ else
2608
+ super
2609
+ end
2610
+ end
2437
2611
  end
2438
2612
  end
@@ -2,51 +2,120 @@ module Sphinx
2
2
  # Pack ints, floats, strings, and arrays to internal representation
3
3
  # needed by Sphinx search engine.
4
4
  #
5
- # @private
6
5
  class Request
7
- # Initialize new request.
6
+ # Initialize new empty request.
7
+ #
8
8
  def initialize
9
9
  @request = ''
10
10
  end
11
-
11
+
12
12
  # Put int(s) to request.
13
+ #
14
+ # @param [Integer] ints one or more integers to put to the request.
15
+ # @return [Request] self.
16
+ #
17
+ # @example
18
+ # request.put_int 10
19
+ # request.put_int 10, 20, 30
20
+ # request.put_int *[10, 20, 30]
21
+ #
13
22
  def put_int(*ints)
14
23
  ints.each { |i| @request << [i].pack('N') }
24
+ self
15
25
  end
16
26
 
17
27
  # Put 64-bit int(s) to request.
28
+ #
29
+ # @param [Integer] ints one or more 64-bit integers to put to the request.
30
+ # @return [Request] self.
31
+ #
32
+ # @example
33
+ # request.put_int64 10
34
+ # request.put_int64 10, 20, 30
35
+ # request.put_int64 *[10, 20, 30]
36
+ #
18
37
  def put_int64(*ints)
19
38
  ints.each { |i| @request << [i].pack('q').reverse }#[i >> 32, i & ((1 << 32) - 1)].pack('NN') }
39
+ self
20
40
  end
21
41
 
22
- # Put string(s) to request (first length, then the string itself).
42
+ # Put strings to request.
43
+ #
44
+ # @param [String] strings one or more strings to put to the request.
45
+ # @return [Request] self.
46
+ #
47
+ # @example
48
+ # request.put_string 'str1'
49
+ # request.put_string 'str1', 'str2', 'str3'
50
+ # request.put_string *['str1', 'str2', 'str3']
51
+ #
23
52
  def put_string(*strings)
24
53
  strings.each { |s| @request << [s.length].pack('N') + s }
54
+ self
25
55
  end
26
-
56
+
27
57
  # Put float(s) to request.
58
+ #
59
+ # @param [Integer, Float] floats one or more floats to put to the request.
60
+ # @return [Request] self.
61
+ #
62
+ # @example
63
+ # request.put_float 10
64
+ # request.put_float 10, 20, 30
65
+ # request.put_float *[10, 20, 30]
66
+ #
28
67
  def put_float(*floats)
29
68
  floats.each do |f|
30
- t1 = [f].pack('f') # machine order
69
+ t1 = [f.to_f].pack('f') # machine order
31
70
  t2 = t1.unpack('L*').first # int in machine order
32
71
  @request << [t2].pack('N')
33
72
  end
73
+ self
34
74
  end
35
-
36
- # Put array of ints to request (first length, then the array itself)
75
+
76
+ # Put array of ints to request.
77
+ #
78
+ # @param [Array<Integer>] arr an array of integers to put to the request.
79
+ # @return [Request] self.
80
+ #
81
+ # @example
82
+ # request.put_int_array [10]
83
+ # request.put_int_array [10, 20, 30]
84
+ #
37
85
  def put_int_array(arr)
38
86
  put_int arr.length, *arr
87
+ self
39
88
  end
40
89
 
41
- # Put array of 64-bit ints to request (first length, then the array itself)
90
+ # Put array of 64-bit ints to request.
91
+ #
92
+ # @param [Array<Integer>] arr an array of integers to put to the request.
93
+ # @return [Request] self.
94
+ #
95
+ # @example
96
+ # request.put_int64_array [10]
97
+ # request.put_int64_array [10, 20, 30]
98
+ #
42
99
  def put_int64_array(arr)
43
- put_int arr.length
100
+ put_int(arr.length)
44
101
  put_int64(*arr)
102
+ self
45
103
  end
46
-
47
- # Returns the entire message
104
+
105
+ # Returns the serialized request.
106
+ #
107
+ # @return [String] serialized request.
108
+ #
48
109
  def to_s
49
110
  @request
50
111
  end
112
+
113
+ # Returns CRC32 of the serialized request.
114
+ #
115
+ # @return [Integer] CRC32 of the serialized request.
116
+ #
117
+ def crc32
118
+ Zlib.crc32(@request)
119
+ end
51
120
  end
52
121
  end
@@ -10,7 +10,7 @@ class Sphinx::Server
10
10
 
11
11
  # The path to UNIX socket where Sphinx server is running on
12
12
  attr_reader :path
13
-
13
+
14
14
  # Creates a new instance of +Server+.
15
15
  #
16
16
  # Parameters:
@@ -24,16 +24,10 @@ class Sphinx::Server
24
24
  @host = host
25
25
  @port = port
26
26
  @path = path
27
-
28
- @timeout = @sphinx.timeout
29
- @retries = @sphinx.retries
30
-
31
- @reqtimeout = @sphinx.reqtimeout
32
- @reqretries = @sphinx.reqretries
33
-
27
+
34
28
  @socket = nil
35
29
  end
36
-
30
+
37
31
  # Gets the opened socket to the server.
38
32
  #
39
33
  # You can pass a block to make any connection establishing related things,
@@ -51,7 +45,7 @@ class Sphinx::Server
51
45
  @socket
52
46
  else
53
47
  socket = nil
54
- Sphinx::safe_execute(@timeout) do
48
+ Sphinx::safe_execute(@sphinx.timeout) do
55
49
  socket = establish_connection
56
50
 
57
51
  # Do custom initialization
@@ -62,26 +56,25 @@ class Sphinx::Server
62
56
  rescue SocketError, SystemCallError, IOError, EOFError, ::Timeout::Error, ::Errno::EPIPE => e
63
57
  # Close previously opened socket (in case of it has been really opened)
64
58
  free_socket(socket, true)
65
-
66
- location = @path || "#{@host}:#{@port}"
67
- error = "connection to #{location} failed ("
59
+
60
+ error = "connection to #{to_s} failed ("
68
61
  if e.kind_of?(SystemCallError)
69
62
  error << "errno=#{e.class::Errno}, "
70
63
  end
71
64
  error << "msg=#{e.message})"
72
65
  raise Sphinx::SphinxConnectError, error
73
66
  end
74
-
67
+
75
68
  # Closes previously opened socket.
76
69
  #
77
70
  # Pass socket retrieved with +get_socket+ method when finished work. It does
78
71
  # not close persistent sockets, but if really you need to do it, pass +true+
79
72
  # as +force+ parameter value.
80
- #
73
+ #
81
74
  def free_socket(socket, force = false)
82
75
  # Socket has not been open
83
76
  return false if socket.nil?
84
-
77
+
85
78
  # Do we try to close persistent socket?
86
79
  if socket == @socket
87
80
  # do not close it if not forced
@@ -98,40 +91,49 @@ class Sphinx::Server
98
91
  true
99
92
  end
100
93
  end
101
-
94
+
102
95
  # Makes specified socket persistent.
103
96
  #
104
97
  # Previous persistent socket will be closed as well.
105
98
  def make_persistent!(socket)
106
- free_socket(@socket)
107
- @socket = socket
99
+ unless socket == @socket
100
+ close_persistent!
101
+ @socket = socket
102
+ end
103
+ @socket
108
104
  end
109
-
105
+
110
106
  # Closes persistent socket.
111
107
  def close_persistent!
112
- if @socket
113
- @socket.close unless @socket.closed?
114
- @socket = nil
115
- true
116
- else
117
- false
118
- end
108
+ free_socket(@socket, true)
119
109
  end
120
-
110
+
121
111
  # Gets a value indicating whether server has persistent socket associated.
122
112
  def persistent?
123
113
  !@socket.nil?
124
114
  end
125
-
115
+
116
+ # Returns a string representation of the sphinx server object.
117
+ #
118
+ def to_s
119
+ @path || "#{@host}:#{@port}"
120
+ end
121
+
122
+ # Returns a string representation of the sphinx server object.
123
+ #
124
+ def inspect
125
+ "<Sphinx::Server: %s%s>" % [to_s, @socket ? '; persistent' : '']
126
+ end
127
+
126
128
  private
127
-
129
+
128
130
  # This is internal method which establishes a connection to a configured server.
129
131
  #
130
132
  # Method configures various socket options (like TCP_NODELAY), and
131
133
  # sets socket timeouts.
132
134
  #
133
135
  # It does not close socket on any failure, please do it from calling code!
134
- #
136
+ #
135
137
  def establish_connection
136
138
  if @path
137
139
  sock = UNIXSocket.new(@path)
@@ -141,30 +143,28 @@ class Sphinx::Server
141
143
 
142
144
  io = Sphinx::BufferedIO.new(sock)
143
145
  io.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
144
- if @reqtimeout > 0
145
- io.read_timeout = @reqtimeout
146
-
146
+ if @sphinx.reqtimeout > 0
147
+ io.read_timeout = @sphinx.reqtimeout
148
+
147
149
  # This is a part of memcache-client library.
148
150
  #
149
151
  # Getting reports from several customers, including 37signals,
150
152
  # that the non-blocking timeouts in 1.7.5 don't seem to be reliable.
151
153
  # It can't hurt to set the underlying socket timeout also, if possible.
152
- if timeout
153
- secs = Integer(timeout)
154
- usecs = Integer((timeout - secs) * 1_000_000)
155
- optval = [secs, usecs].pack("l_2")
156
- begin
157
- io.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
158
- io.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
159
- rescue Exception => ex
160
- # Solaris, for one, does not like/support socket timeouts.
161
- @warning = "Unable to use raw socket timeouts: #{ex.class.name}: #{ex.message}"
162
- end
154
+ secs = Integer(@sphinx.reqtimeout)
155
+ usecs = Integer((@sphinx.reqtimeout - secs) * 1_000_000)
156
+ optval = [secs, usecs].pack("l_2")
157
+ begin
158
+ io.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
159
+ io.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
160
+ rescue Exception => ex
161
+ # Solaris, for one, does not like/support socket timeouts.
162
+ @sphinx.logger.warn { "[sphinx] Unable to use raw socket timeouts: #{ex.class.name}: #{ex.message}" } if @sphinx.logger
163
163
  end
164
164
  else
165
165
  io.read_timeout = false
166
166
  end
167
-
167
+
168
168
  io
169
169
  end
170
170
  end
@@ -26,35 +26,26 @@ describe Sphinx::Client, 'disconnected' do
26
26
  end
27
27
  end
28
28
 
29
- it 'should round-robin servers on each call' do
29
+ it 'should select server based on index' do
30
30
  @sphinx.SetServers(@servers)
31
31
  cnt = 0
32
- @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[0] }
32
+ @sphinx.send(:with_server, 0) { |server| cnt += 1; server.should == @sphinx.servers[0] }
33
33
  cnt.should == 1
34
34
  cnt = 0
35
- @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[1] }
35
+ @sphinx.send(:with_server, 1) { |server| cnt += 1; server.should == @sphinx.servers[1] }
36
36
  cnt.should == 1
37
37
  cnt = 0
38
- @sphinx.send(:with_server) { |server| cnt += 1; server.should == @sphinx.servers[0] }
38
+ @sphinx.send(:with_server, 2) { |server| cnt += 1; server.should == @sphinx.servers[0] }
39
39
  cnt.should == 1
40
40
  end
41
41
 
42
- it 'should round-robin servers and raise an exception on error' do
42
+ it 'should select given server' do
43
43
  @sphinx.SetServers(@servers)
44
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)
45
+ @sphinx.send(:with_server, @sphinx.servers[0]) { |server| cnt += 1; server.should == @sphinx.servers[0] }
53
46
  cnt.should == 1
54
47
  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)
48
+ @sphinx.send(:with_server, @sphinx.servers[1]) { |server| cnt += 1; server.should == @sphinx.servers[1] }
58
49
  cnt.should == 1
59
50
  end
60
51
  end
@@ -80,6 +71,24 @@ describe Sphinx::Client, 'disconnected' do
80
71
  }.to raise_error(Sphinx::SphinxConnectError)
81
72
  cnt.should == 3
82
73
  end
74
+
75
+ it 'should round-robin servers with respect to passed index and raise an exception on error' do
76
+ @sphinx.SetServers(@servers)
77
+ cnt = 0
78
+ expect {
79
+ @sphinx.send(:with_server, 1) { |server| cnt += 1; server.should == @sphinx.servers[cnt % 2]; raise Sphinx::SphinxConnectError }
80
+ }.to raise_error(Sphinx::SphinxConnectError)
81
+ cnt.should == 3
82
+ end
83
+
84
+ it 'should round-robin with respect to attempts number passed' do
85
+ @sphinx.SetServers(@servers)
86
+ cnt = 0
87
+ expect {
88
+ @sphinx.send(:with_server, 0, 5) { |server| cnt += 1; server.should == @sphinx.servers[(cnt - 1) % 2]; raise Sphinx::SphinxConnectError }
89
+ }.to raise_error(Sphinx::SphinxConnectError)
90
+ cnt.should == 5
91
+ end
83
92
  end
84
93
  end
85
94
 
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{sphinx}
8
- s.version = "0.9.10.2091"
8
+ s.version = "0.9.10.2094"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Dmytro Shteflyuk"]
12
- s.date = %q{2009-11-20}
12
+ s.date = %q{2009-11-23}
13
13
  s.description = %q{An easy interface to Sphinx standalone full-text search engine. It is implemented as plugin for Ruby on Rails, but can be easily used as standalone library.}
14
14
  s.email = %q{kpumuk@kpumuk.info}
15
15
  s.extra_rdoc_files = [
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sphinx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.10.2091
4
+ version: 0.9.10.2094
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmytro Shteflyuk
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-20 00:00:00 +02:00
12
+ date: 2009-11-23 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies: []
15
15