sphinx 0.9.10.2091 → 0.9.10.2094

Sign up to get free protection for your applications and to get access to all the features.
@@ -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