sphinx 0.9.10.2091 → 0.9.10.2094
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|