sphinx 0.9.9.2117
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/README.rdoc +243 -0
- data/Rakefile +45 -0
- data/VERSION.yml +5 -0
- data/init.rb +1 -0
- data/lib/sphinx/buffered_io.rb +26 -0
- data/lib/sphinx/client.rb +2426 -0
- data/lib/sphinx/constants.rb +179 -0
- data/lib/sphinx/indifferent_access.rb +152 -0
- data/lib/sphinx/request.rb +121 -0
- data/lib/sphinx/response.rb +71 -0
- data/lib/sphinx/server.rb +170 -0
- data/lib/sphinx/timeout.rb +31 -0
- data/lib/sphinx.rb +51 -0
- data/spec/client_response_spec.rb +170 -0
- data/spec/client_spec.rb +669 -0
- data/spec/client_validations_spec.rb +859 -0
- data/spec/fixtures/default_search.php +8 -0
- data/spec/fixtures/default_search_index.php +8 -0
- data/spec/fixtures/excerpt_custom.php +11 -0
- data/spec/fixtures/excerpt_default.php +8 -0
- data/spec/fixtures/excerpt_flags.php +12 -0
- data/spec/fixtures/field_weights.php +9 -0
- data/spec/fixtures/filter.php +9 -0
- data/spec/fixtures/filter_exclude.php +9 -0
- data/spec/fixtures/filter_float_range.php +9 -0
- data/spec/fixtures/filter_float_range_exclude.php +9 -0
- data/spec/fixtures/filter_range.php +9 -0
- data/spec/fixtures/filter_range_exclude.php +9 -0
- data/spec/fixtures/filter_range_int64.php +10 -0
- data/spec/fixtures/filter_ranges.php +10 -0
- data/spec/fixtures/filters.php +10 -0
- data/spec/fixtures/filters_different.php +13 -0
- data/spec/fixtures/geo_anchor.php +9 -0
- data/spec/fixtures/group_by_attr.php +9 -0
- data/spec/fixtures/group_by_attrpair.php +9 -0
- data/spec/fixtures/group_by_day.php +9 -0
- data/spec/fixtures/group_by_day_sort.php +9 -0
- data/spec/fixtures/group_by_month.php +9 -0
- data/spec/fixtures/group_by_week.php +9 -0
- data/spec/fixtures/group_by_year.php +9 -0
- data/spec/fixtures/group_distinct.php +10 -0
- data/spec/fixtures/id_range.php +9 -0
- data/spec/fixtures/id_range64.php +9 -0
- data/spec/fixtures/index_weights.php +9 -0
- data/spec/fixtures/keywords.php +8 -0
- data/spec/fixtures/limits.php +9 -0
- data/spec/fixtures/limits_cutoff.php +9 -0
- data/spec/fixtures/limits_max.php +9 -0
- data/spec/fixtures/limits_max_cutoff.php +9 -0
- data/spec/fixtures/match_all.php +9 -0
- data/spec/fixtures/match_any.php +9 -0
- data/spec/fixtures/match_boolean.php +9 -0
- data/spec/fixtures/match_extended.php +9 -0
- data/spec/fixtures/match_extended2.php +9 -0
- data/spec/fixtures/match_fullscan.php +9 -0
- data/spec/fixtures/match_phrase.php +9 -0
- data/spec/fixtures/max_query_time.php +9 -0
- data/spec/fixtures/miltiple_queries.php +12 -0
- data/spec/fixtures/ranking_bm25.php +9 -0
- data/spec/fixtures/ranking_fieldmask.php +9 -0
- data/spec/fixtures/ranking_matchany.php +9 -0
- data/spec/fixtures/ranking_none.php +9 -0
- data/spec/fixtures/ranking_proximity.php +9 -0
- data/spec/fixtures/ranking_proximity_bm25.php +9 -0
- data/spec/fixtures/ranking_wordcount.php +9 -0
- data/spec/fixtures/retries.php +9 -0
- data/spec/fixtures/retries_delay.php +9 -0
- data/spec/fixtures/select.php +9 -0
- data/spec/fixtures/set_override.php +11 -0
- data/spec/fixtures/sort_attr_asc.php +9 -0
- data/spec/fixtures/sort_attr_desc.php +9 -0
- data/spec/fixtures/sort_expr.php +9 -0
- data/spec/fixtures/sort_extended.php +9 -0
- data/spec/fixtures/sort_relevance.php +9 -0
- data/spec/fixtures/sort_time_segments.php +9 -0
- data/spec/fixtures/sphinxapi.php +1633 -0
- data/spec/fixtures/update_attributes.php +8 -0
- data/spec/fixtures/update_attributes_mva.php +8 -0
- data/spec/fixtures/weights.php +9 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/sphinx/sphinx-id64.conf +67 -0
- data/spec/sphinx/sphinx.conf +67 -0
- data/spec/sphinx/sphinx_test.sql +88 -0
- data/sphinx.gemspec +127 -0
- metadata +142 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
module Sphinx
|
2
|
+
# Contains all constants need by Sphinx API.
|
3
|
+
#
|
4
|
+
module Constants
|
5
|
+
#=================================================================
|
6
|
+
# Known searchd commands
|
7
|
+
#=================================================================
|
8
|
+
|
9
|
+
# search command
|
10
|
+
# @private
|
11
|
+
SEARCHD_COMMAND_SEARCH = 0
|
12
|
+
# excerpt command
|
13
|
+
# @private
|
14
|
+
SEARCHD_COMMAND_EXCERPT = 1
|
15
|
+
# update command
|
16
|
+
# @private
|
17
|
+
SEARCHD_COMMAND_UPDATE = 2
|
18
|
+
# keywords command
|
19
|
+
# @private
|
20
|
+
SEARCHD_COMMAND_KEYWORDS = 3
|
21
|
+
# persist command
|
22
|
+
# @private
|
23
|
+
SEARCHD_COMMAND_PERSIST = 4
|
24
|
+
# status command
|
25
|
+
# @private
|
26
|
+
SEARCHD_COMMAND_STATUS = 5
|
27
|
+
# query command
|
28
|
+
# @private
|
29
|
+
SEARCHD_COMMAND_QUERY = 6
|
30
|
+
|
31
|
+
#=================================================================
|
32
|
+
# Current client-side command implementation versions
|
33
|
+
#=================================================================
|
34
|
+
|
35
|
+
# search command version
|
36
|
+
# @private
|
37
|
+
VER_COMMAND_SEARCH = 0x116
|
38
|
+
# excerpt command version
|
39
|
+
# @private
|
40
|
+
VER_COMMAND_EXCERPT = 0x100
|
41
|
+
# update command version
|
42
|
+
# @private
|
43
|
+
VER_COMMAND_UPDATE = 0x102
|
44
|
+
# keywords command version
|
45
|
+
# @private
|
46
|
+
VER_COMMAND_KEYWORDS = 0x100
|
47
|
+
# persist command version
|
48
|
+
# @private
|
49
|
+
VER_COMMAND_PERSIST = 0x000
|
50
|
+
# status command version
|
51
|
+
# @private
|
52
|
+
VER_COMMAND_STATUS = 0x100
|
53
|
+
# query command version
|
54
|
+
# @private
|
55
|
+
VER_COMMAND_QUERY = 0x100
|
56
|
+
|
57
|
+
#=================================================================
|
58
|
+
# Known searchd status codes
|
59
|
+
#=================================================================
|
60
|
+
|
61
|
+
# general success, command-specific reply follows
|
62
|
+
# @private
|
63
|
+
SEARCHD_OK = 0
|
64
|
+
# general failure, command-specific reply may follow
|
65
|
+
# @private
|
66
|
+
SEARCHD_ERROR = 1
|
67
|
+
# temporaty failure, client should retry later
|
68
|
+
# @private
|
69
|
+
SEARCHD_RETRY = 2
|
70
|
+
# general success, warning message and command-specific reply follow
|
71
|
+
# @private
|
72
|
+
SEARCHD_WARNING = 3
|
73
|
+
|
74
|
+
#=================================================================
|
75
|
+
# Known match modes
|
76
|
+
#=================================================================
|
77
|
+
|
78
|
+
# match all query words
|
79
|
+
SPH_MATCH_ALL = 0
|
80
|
+
# match any query word
|
81
|
+
SPH_MATCH_ANY = 1
|
82
|
+
# match this exact phrase
|
83
|
+
SPH_MATCH_PHRASE = 2
|
84
|
+
# match this boolean query
|
85
|
+
SPH_MATCH_BOOLEAN = 3
|
86
|
+
# match this extended query
|
87
|
+
SPH_MATCH_EXTENDED = 4
|
88
|
+
# match all document IDs w/o fulltext query, apply filters
|
89
|
+
SPH_MATCH_FULLSCAN = 5
|
90
|
+
# extended engine V2 (TEMPORARY, WILL BE REMOVED IN 0.9.8-RELEASE)
|
91
|
+
SPH_MATCH_EXTENDED2 = 6
|
92
|
+
|
93
|
+
#=================================================================
|
94
|
+
# Known ranking modes (ext2 only)
|
95
|
+
#=================================================================
|
96
|
+
|
97
|
+
# default mode, phrase proximity major factor and BM25 minor one
|
98
|
+
SPH_RANK_PROXIMITY_BM25 = 0
|
99
|
+
# statistical mode, BM25 ranking only (faster but worse quality)
|
100
|
+
SPH_RANK_BM25 = 1
|
101
|
+
# no ranking, all matches get a weight of 1
|
102
|
+
SPH_RANK_NONE = 2
|
103
|
+
# simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
|
104
|
+
SPH_RANK_WORDCOUNT = 3
|
105
|
+
# phrase proximity
|
106
|
+
SPH_RANK_PROXIMITY = 4
|
107
|
+
# emulate old match-any weighting
|
108
|
+
SPH_RANK_MATCHANY = 5
|
109
|
+
# sets bits where there were matches
|
110
|
+
SPH_RANK_FIELDMASK = 6
|
111
|
+
|
112
|
+
#=================================================================
|
113
|
+
# Known sort modes
|
114
|
+
#=================================================================
|
115
|
+
|
116
|
+
# sort by document relevance desc, then by date
|
117
|
+
SPH_SORT_RELEVANCE = 0
|
118
|
+
# sort by document date desc, then by relevance desc
|
119
|
+
SPH_SORT_ATTR_DESC = 1
|
120
|
+
# sort by document date asc, then by relevance desc
|
121
|
+
SPH_SORT_ATTR_ASC = 2
|
122
|
+
# sort by time segments (hour/day/week/etc) desc, then by relevance desc
|
123
|
+
SPH_SORT_TIME_SEGMENTS = 3
|
124
|
+
# sort by SQL-like expression (eg. "@relevance DESC, price ASC, @id DESC")
|
125
|
+
SPH_SORT_EXTENDED = 4
|
126
|
+
# sort by arithmetic expression in descending order (eg. "@id + max(@weight,1000)*boost + log(price)")
|
127
|
+
SPH_SORT_EXPR = 5
|
128
|
+
|
129
|
+
#=================================================================
|
130
|
+
# Known filter types
|
131
|
+
#=================================================================
|
132
|
+
|
133
|
+
# filter by integer values set
|
134
|
+
SPH_FILTER_VALUES = 0
|
135
|
+
# filter by integer range
|
136
|
+
SPH_FILTER_RANGE = 1
|
137
|
+
# filter by float range
|
138
|
+
SPH_FILTER_FLOATRANGE = 2
|
139
|
+
|
140
|
+
#=================================================================
|
141
|
+
# Known attribute types
|
142
|
+
#=================================================================
|
143
|
+
|
144
|
+
# this attr is just an integer
|
145
|
+
SPH_ATTR_INTEGER = 1
|
146
|
+
# this attr is a timestamp
|
147
|
+
SPH_ATTR_TIMESTAMP = 2
|
148
|
+
# this attr is an ordinal string number (integer at search time,
|
149
|
+
# specially handled at indexing time)
|
150
|
+
SPH_ATTR_ORDINAL = 3
|
151
|
+
# this attr is a boolean bit field
|
152
|
+
SPH_ATTR_BOOL = 4
|
153
|
+
# this attr is a float
|
154
|
+
SPH_ATTR_FLOAT = 5
|
155
|
+
# signed 64-bit integer
|
156
|
+
SPH_ATTR_BIGINT = 6
|
157
|
+
# this attr has multiple values (0 or more)
|
158
|
+
SPH_ATTR_MULTI = 0x40000000
|
159
|
+
|
160
|
+
#=================================================================
|
161
|
+
# Known grouping functions
|
162
|
+
#=================================================================
|
163
|
+
|
164
|
+
# group by day
|
165
|
+
SPH_GROUPBY_DAY = 0
|
166
|
+
# group by week
|
167
|
+
SPH_GROUPBY_WEEK = 1
|
168
|
+
# group by month
|
169
|
+
SPH_GROUPBY_MONTH = 2
|
170
|
+
# group by year
|
171
|
+
SPH_GROUPBY_YEAR = 3
|
172
|
+
# group by attribute value
|
173
|
+
SPH_GROUPBY_ATTR = 4
|
174
|
+
# group by sequential attrs pair
|
175
|
+
SPH_GROUPBY_ATTRPAIR = 5
|
176
|
+
end
|
177
|
+
|
178
|
+
include Constants
|
179
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Sphinx
|
2
|
+
begin
|
3
|
+
HashWithIndifferentAccess = ::HashWithIndifferentAccess
|
4
|
+
rescue NameError
|
5
|
+
# This class has dubious semantics and we only have it so that
|
6
|
+
# people can write params[:key] instead of params['key']
|
7
|
+
# and they get the same value for both keys.
|
8
|
+
#
|
9
|
+
# This is part of Rails' ActiveSupport project. If you are
|
10
|
+
# using Rails, this class will not be used.
|
11
|
+
#
|
12
|
+
class HashWithIndifferentAccess < Hash
|
13
|
+
def initialize(constructor = {})
|
14
|
+
if constructor.is_a?(Hash)
|
15
|
+
super()
|
16
|
+
update(constructor)
|
17
|
+
else
|
18
|
+
super(constructor)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def default(key = nil)
|
23
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
24
|
+
self[key]
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
31
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
32
|
+
|
33
|
+
# Assigns a new value to the hash:
|
34
|
+
#
|
35
|
+
# hash = HashWithIndifferentAccess.new
|
36
|
+
# hash[:key] = "value"
|
37
|
+
#
|
38
|
+
def []=(key, value)
|
39
|
+
regular_writer(convert_key(key), convert_value(value))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Updates the instantized hash with values from the second:
|
43
|
+
#
|
44
|
+
# hash_1 = HashWithIndifferentAccess.new
|
45
|
+
# hash_1[:key] = "value"
|
46
|
+
#
|
47
|
+
# hash_2 = HashWithIndifferentAccess.new
|
48
|
+
# hash_2[:key] = "New Value!"
|
49
|
+
#
|
50
|
+
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
51
|
+
#
|
52
|
+
def update(other_hash)
|
53
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :merge!, :update
|
58
|
+
|
59
|
+
# Checks the hash for a key matching the argument passed in:
|
60
|
+
#
|
61
|
+
# hash = HashWithIndifferentAccess.new
|
62
|
+
# hash["key"] = "value"
|
63
|
+
# hash.key? :key # => true
|
64
|
+
# hash.key? "key" # => true
|
65
|
+
#
|
66
|
+
def key?(key)
|
67
|
+
super(convert_key(key))
|
68
|
+
end
|
69
|
+
|
70
|
+
alias_method :include?, :key?
|
71
|
+
alias_method :has_key?, :key?
|
72
|
+
alias_method :member?, :key?
|
73
|
+
|
74
|
+
# Fetches the value for the specified key, same as doing hash[key]
|
75
|
+
def fetch(key, *extras)
|
76
|
+
super(convert_key(key), *extras)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns an array of the values at the specified indices:
|
80
|
+
#
|
81
|
+
# hash = HashWithIndifferentAccess.new
|
82
|
+
# hash[:a] = "x"
|
83
|
+
# hash[:b] = "y"
|
84
|
+
# hash.values_at("a", "b") # => ["x", "y"]
|
85
|
+
#
|
86
|
+
def values_at(*indices)
|
87
|
+
indices.collect {|key| self[convert_key(key)]}
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns an exact copy of the hash.
|
91
|
+
def dup
|
92
|
+
HashWithIndifferentAccess.new(self)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
|
96
|
+
# Does not overwrite the existing hash.
|
97
|
+
def merge(hash)
|
98
|
+
self.dup.update(hash)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
|
102
|
+
# This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
|
103
|
+
def reverse_merge(other_hash)
|
104
|
+
super other_hash.with_indifferent_access
|
105
|
+
end
|
106
|
+
|
107
|
+
# Removes a specified key from the hash.
|
108
|
+
def delete(key)
|
109
|
+
super(convert_key(key))
|
110
|
+
end
|
111
|
+
|
112
|
+
def stringify_keys!; self end
|
113
|
+
def symbolize_keys!; self end
|
114
|
+
def to_options!; self end
|
115
|
+
|
116
|
+
# Convert to a Hash with String keys.
|
117
|
+
def to_hash
|
118
|
+
Hash.new(default).merge(self)
|
119
|
+
end
|
120
|
+
|
121
|
+
protected
|
122
|
+
def convert_key(key)
|
123
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
124
|
+
end
|
125
|
+
|
126
|
+
def convert_value(value)
|
127
|
+
case value
|
128
|
+
when Hash
|
129
|
+
value.with_indifferent_access
|
130
|
+
when Array
|
131
|
+
value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
|
132
|
+
else
|
133
|
+
value
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
module HashIndifferentAccess #:nodoc:
|
139
|
+
def with_indifferent_access
|
140
|
+
hash = HashWithIndifferentAccess.new(self)
|
141
|
+
hash.default = self.default
|
142
|
+
hash
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class ::Hash #:nodoc:
|
147
|
+
unless respond_to?(:with_indifferent_access)
|
148
|
+
include Sphinx::HashIndifferentAccess
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Sphinx
|
2
|
+
# Pack ints, floats, strings, and arrays to internal representation
|
3
|
+
# needed by Sphinx search engine.
|
4
|
+
#
|
5
|
+
class Request
|
6
|
+
# Initialize new empty request.
|
7
|
+
#
|
8
|
+
def initialize
|
9
|
+
@request = ''
|
10
|
+
end
|
11
|
+
|
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
|
+
#
|
22
|
+
def put_int(*ints)
|
23
|
+
ints.each { |i| @request << [i].pack('N') }
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
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
|
+
#
|
37
|
+
def put_int64(*ints)
|
38
|
+
ints.each { |i| @request << [i].pack('q').reverse }#[i >> 32, i & ((1 << 32) - 1)].pack('NN') }
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
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
|
+
#
|
52
|
+
def put_string(*strings)
|
53
|
+
strings.each { |s| @request << [s.length].pack('N') + s }
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
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
|
+
#
|
67
|
+
def put_float(*floats)
|
68
|
+
floats.each do |f|
|
69
|
+
t1 = [f.to_f].pack('f') # machine order
|
70
|
+
t2 = t1.unpack('L*').first # int in machine order
|
71
|
+
@request << [t2].pack('N')
|
72
|
+
end
|
73
|
+
self
|
74
|
+
end
|
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
|
+
#
|
85
|
+
def put_int_array(arr)
|
86
|
+
put_int arr.length, *arr
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
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
|
+
#
|
99
|
+
def put_int64_array(arr)
|
100
|
+
put_int(arr.length)
|
101
|
+
put_int64(*arr)
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the serialized request.
|
106
|
+
#
|
107
|
+
# @return [String] serialized request.
|
108
|
+
#
|
109
|
+
def to_s
|
110
|
+
@request
|
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
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Sphinx
|
2
|
+
# Unpack internal Sphinx representation of ints, floats, strings, and arrays.
|
3
|
+
# needed by Sphinx search engine.
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
class Response
|
7
|
+
# Initialize new request.
|
8
|
+
def initialize(response)
|
9
|
+
@response = response
|
10
|
+
@position = 0
|
11
|
+
@size = response.length
|
12
|
+
end
|
13
|
+
|
14
|
+
# Gets current stream position.
|
15
|
+
def position
|
16
|
+
@position
|
17
|
+
end
|
18
|
+
|
19
|
+
# Gets response size.
|
20
|
+
def size
|
21
|
+
@size
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns <tt>true</tt> when response stream is out.
|
25
|
+
def eof?
|
26
|
+
@position >= @size
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get int from stream.
|
30
|
+
def get_int
|
31
|
+
raise EOFError if @position + 4 > @size
|
32
|
+
value = @response[@position, 4].unpack('N*').first
|
33
|
+
@position += 4
|
34
|
+
return value
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get 64-bit int from stream.
|
38
|
+
def get_int64
|
39
|
+
raise EOFError if @position + 8 > @size
|
40
|
+
hi, lo = @response[@position, 8].unpack('N*N*')
|
41
|
+
@position += 8
|
42
|
+
return (hi << 32) + lo
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get array of <tt>count</tt> ints from stream.
|
46
|
+
def get_ints(count)
|
47
|
+
length = 4 * count
|
48
|
+
raise EOFError if @position + length > @size
|
49
|
+
values = @response[@position, length].unpack('N*' * count)
|
50
|
+
@position += length
|
51
|
+
return values
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get string from stream.
|
55
|
+
def get_string
|
56
|
+
length = get_int
|
57
|
+
raise EOFError if @position + length > @size
|
58
|
+
value = length > 0 ? @response[@position, length] : ''
|
59
|
+
@position += length
|
60
|
+
return value
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get float from stream.
|
64
|
+
def get_float
|
65
|
+
raise EOFError if @position + 4 > @size
|
66
|
+
uval = @response[@position, 4].unpack('N*').first;
|
67
|
+
@position += 4
|
68
|
+
return ([uval].pack('L')).unpack('f*').first
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# Represents an instance of searchd server.
|
2
|
+
#
|
3
|
+
# @private
|
4
|
+
class Sphinx::Server
|
5
|
+
# The host the Sphinx server is running on
|
6
|
+
attr_reader :host
|
7
|
+
|
8
|
+
# The port the Sphinx server is listening on
|
9
|
+
attr_reader :port
|
10
|
+
|
11
|
+
# The path to UNIX socket where Sphinx server is running on
|
12
|
+
attr_reader :path
|
13
|
+
|
14
|
+
# Creates a new instance of +Server+.
|
15
|
+
#
|
16
|
+
# Parameters:
|
17
|
+
# * +sphinx+ -- an instance of <tt>Sphinx::Client</tt>.
|
18
|
+
# * +host+ -- name of host where search is running (if +path+ is not specified).
|
19
|
+
# * +port+ -- searchd port (if +path+ is not specified).
|
20
|
+
# * +path+ -- an absolute path to the UNIX socket.
|
21
|
+
#
|
22
|
+
def initialize(sphinx, host, port, path)
|
23
|
+
@sphinx = sphinx
|
24
|
+
@host = host
|
25
|
+
@port = port
|
26
|
+
@path = path
|
27
|
+
|
28
|
+
@socket = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# Gets the opened socket to the server.
|
32
|
+
#
|
33
|
+
# You can pass a block to make any connection establishing related things,
|
34
|
+
# like protocol version interchange. They will be treated as a part of
|
35
|
+
# connection routine, so connection timeout will include them.
|
36
|
+
#
|
37
|
+
# In case of connection error, +SphinxConnectError+ exception will be raised.
|
38
|
+
#
|
39
|
+
# Method returns opened socket, so do not forget to close it using +free_socket+
|
40
|
+
# method. Make sure you will close socket in case of any emergency.
|
41
|
+
#
|
42
|
+
def get_socket(&block)
|
43
|
+
if persistent?
|
44
|
+
yield @socket
|
45
|
+
@socket
|
46
|
+
else
|
47
|
+
socket = nil
|
48
|
+
Sphinx::safe_execute(@sphinx.timeout) do
|
49
|
+
socket = establish_connection
|
50
|
+
|
51
|
+
# Do custom initialization
|
52
|
+
yield socket if block_given?
|
53
|
+
end
|
54
|
+
socket
|
55
|
+
end
|
56
|
+
rescue SocketError, SystemCallError, IOError, EOFError, ::Timeout::Error, ::Errno::EPIPE => e
|
57
|
+
# Close previously opened socket (in case of it has been really opened)
|
58
|
+
free_socket(socket, true)
|
59
|
+
|
60
|
+
error = "connection to #{to_s} failed ("
|
61
|
+
if e.kind_of?(SystemCallError)
|
62
|
+
error << "errno=#{e.class::Errno}, "
|
63
|
+
end
|
64
|
+
error << "msg=#{e.message})"
|
65
|
+
raise Sphinx::SphinxConnectError, error
|
66
|
+
end
|
67
|
+
|
68
|
+
# Closes previously opened socket.
|
69
|
+
#
|
70
|
+
# Pass socket retrieved with +get_socket+ method when finished work. It does
|
71
|
+
# not close persistent sockets, but if really you need to do it, pass +true+
|
72
|
+
# as +force+ parameter value.
|
73
|
+
#
|
74
|
+
def free_socket(socket, force = false)
|
75
|
+
# Socket has not been open
|
76
|
+
return false if socket.nil?
|
77
|
+
|
78
|
+
# Do we try to close persistent socket?
|
79
|
+
if socket == @socket
|
80
|
+
# do not close it if not forced
|
81
|
+
if force
|
82
|
+
@socket.close unless @socket.closed?
|
83
|
+
@socket = nil
|
84
|
+
true
|
85
|
+
else
|
86
|
+
false
|
87
|
+
end
|
88
|
+
else
|
89
|
+
# Just close this socket
|
90
|
+
socket.close unless socket.closed?
|
91
|
+
true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Makes specified socket persistent.
|
96
|
+
#
|
97
|
+
# Previous persistent socket will be closed as well.
|
98
|
+
def make_persistent!(socket)
|
99
|
+
unless socket == @socket
|
100
|
+
close_persistent!
|
101
|
+
@socket = socket
|
102
|
+
end
|
103
|
+
@socket
|
104
|
+
end
|
105
|
+
|
106
|
+
# Closes persistent socket.
|
107
|
+
def close_persistent!
|
108
|
+
free_socket(@socket, true)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Gets a value indicating whether server has persistent socket associated.
|
112
|
+
def persistent?
|
113
|
+
!@socket.nil?
|
114
|
+
end
|
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
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# This is internal method which establishes a connection to a configured server.
|
131
|
+
#
|
132
|
+
# Method configures various socket options (like TCP_NODELAY), and
|
133
|
+
# sets socket timeouts.
|
134
|
+
#
|
135
|
+
# It does not close socket on any failure, please do it from calling code!
|
136
|
+
#
|
137
|
+
def establish_connection
|
138
|
+
if @path
|
139
|
+
sock = UNIXSocket.new(@path)
|
140
|
+
else
|
141
|
+
sock = TCPSocket.new(@host, @port)
|
142
|
+
end
|
143
|
+
|
144
|
+
io = Sphinx::BufferedIO.new(sock)
|
145
|
+
io.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
146
|
+
if @sphinx.reqtimeout > 0
|
147
|
+
io.read_timeout = @sphinx.reqtimeout
|
148
|
+
|
149
|
+
# This is a part of memcache-client library.
|
150
|
+
#
|
151
|
+
# Getting reports from several customers, including 37signals,
|
152
|
+
# that the non-blocking timeouts in 1.7.5 don't seem to be reliable.
|
153
|
+
# It can't hurt to set the underlying socket timeout also, if possible.
|
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
|
+
end
|
164
|
+
else
|
165
|
+
io.read_timeout = false
|
166
|
+
end
|
167
|
+
|
168
|
+
io
|
169
|
+
end
|
170
|
+
end
|