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.
- data/README.rdoc +21 -0
- data/VERSION.yml +1 -1
- data/lib/sphinx.rb +6 -1
- data/lib/sphinx/client.rb +249 -75
- data/lib/sphinx/request.rb +81 -12
- data/lib/sphinx/server.rb +46 -46
- data/spec/client_spec.rb +25 -16
- data/sphinx.gemspec +2 -2
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -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,
|
data/VERSION.yml
CHANGED
data/lib/sphinx.rb
CHANGED
@@ -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'
|
data/lib/sphinx/client.rb
CHANGED
@@ -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
|
-
@
|
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.
|
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.
|
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.
|
474
|
-
raise ArgumentError, '"retries" argument must be Integer' unless retries.
|
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.
|
512
|
-
raise ArgumentError, '"retries" argument must be Integer' unless retries.
|
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.
|
543
|
-
raise ArgumentError, '"delay" argument must be Integer' unless delay.
|
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.
|
600
|
-
raise ArgumentError, '"limit" argument must be Integer' unless limit.
|
601
|
-
raise ArgumentError, '"max" argument must be Integer' unless max.
|
602
|
-
raise ArgumentError, '"cutoff" argument must be Integer' unless cutoff.
|
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.
|
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.
|
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
|
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
|
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.
|
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.
|
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
|
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
|
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.
|
1028
|
-
raise ArgumentError, '"max" argument must be Integer' unless max.
|
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.
|
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.
|
1116
|
-
raise ArgumentError, '"max" argument must be Integer' unless max.
|
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 [
|
1141
|
-
# @param [
|
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
|
1159
|
-
raise ArgumentError, '"max" argument must be
|
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 [
|
1189
|
-
# @param [
|
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
|
1203
|
-
raise ArgumentError, '"long" argument must be
|
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
|
-
|
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.
|
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.
|
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.
|
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
|
-
|
2101
|
-
|
2102
|
-
|
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 [
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/sphinx/request.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
100
|
+
put_int(arr.length)
|
44
101
|
put_int64(*arr)
|
102
|
+
self
|
45
103
|
end
|
46
|
-
|
47
|
-
# Returns the
|
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
|
data/lib/sphinx/server.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
107
|
-
|
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
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
data/spec/client_spec.rb
CHANGED
@@ -26,35 +26,26 @@ describe Sphinx::Client, 'disconnected' do
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
it 'should
|
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
|
42
|
+
it 'should select given server' do
|
43
43
|
@sphinx.SetServers(@servers)
|
44
44
|
cnt = 0
|
45
|
-
|
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
|
-
|
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
|
|
data/sphinx.gemspec
CHANGED
@@ -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.
|
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-
|
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.
|
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-
|
12
|
+
date: 2009-11-23 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|