sphinx 0.9.9.2117
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/.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
|