sphinx 0.9.9.2117

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/.gitignore +4 -0
  2. data/README.rdoc +243 -0
  3. data/Rakefile +45 -0
  4. data/VERSION.yml +5 -0
  5. data/init.rb +1 -0
  6. data/lib/sphinx/buffered_io.rb +26 -0
  7. data/lib/sphinx/client.rb +2426 -0
  8. data/lib/sphinx/constants.rb +179 -0
  9. data/lib/sphinx/indifferent_access.rb +152 -0
  10. data/lib/sphinx/request.rb +121 -0
  11. data/lib/sphinx/response.rb +71 -0
  12. data/lib/sphinx/server.rb +170 -0
  13. data/lib/sphinx/timeout.rb +31 -0
  14. data/lib/sphinx.rb +51 -0
  15. data/spec/client_response_spec.rb +170 -0
  16. data/spec/client_spec.rb +669 -0
  17. data/spec/client_validations_spec.rb +859 -0
  18. data/spec/fixtures/default_search.php +8 -0
  19. data/spec/fixtures/default_search_index.php +8 -0
  20. data/spec/fixtures/excerpt_custom.php +11 -0
  21. data/spec/fixtures/excerpt_default.php +8 -0
  22. data/spec/fixtures/excerpt_flags.php +12 -0
  23. data/spec/fixtures/field_weights.php +9 -0
  24. data/spec/fixtures/filter.php +9 -0
  25. data/spec/fixtures/filter_exclude.php +9 -0
  26. data/spec/fixtures/filter_float_range.php +9 -0
  27. data/spec/fixtures/filter_float_range_exclude.php +9 -0
  28. data/spec/fixtures/filter_range.php +9 -0
  29. data/spec/fixtures/filter_range_exclude.php +9 -0
  30. data/spec/fixtures/filter_range_int64.php +10 -0
  31. data/spec/fixtures/filter_ranges.php +10 -0
  32. data/spec/fixtures/filters.php +10 -0
  33. data/spec/fixtures/filters_different.php +13 -0
  34. data/spec/fixtures/geo_anchor.php +9 -0
  35. data/spec/fixtures/group_by_attr.php +9 -0
  36. data/spec/fixtures/group_by_attrpair.php +9 -0
  37. data/spec/fixtures/group_by_day.php +9 -0
  38. data/spec/fixtures/group_by_day_sort.php +9 -0
  39. data/spec/fixtures/group_by_month.php +9 -0
  40. data/spec/fixtures/group_by_week.php +9 -0
  41. data/spec/fixtures/group_by_year.php +9 -0
  42. data/spec/fixtures/group_distinct.php +10 -0
  43. data/spec/fixtures/id_range.php +9 -0
  44. data/spec/fixtures/id_range64.php +9 -0
  45. data/spec/fixtures/index_weights.php +9 -0
  46. data/spec/fixtures/keywords.php +8 -0
  47. data/spec/fixtures/limits.php +9 -0
  48. data/spec/fixtures/limits_cutoff.php +9 -0
  49. data/spec/fixtures/limits_max.php +9 -0
  50. data/spec/fixtures/limits_max_cutoff.php +9 -0
  51. data/spec/fixtures/match_all.php +9 -0
  52. data/spec/fixtures/match_any.php +9 -0
  53. data/spec/fixtures/match_boolean.php +9 -0
  54. data/spec/fixtures/match_extended.php +9 -0
  55. data/spec/fixtures/match_extended2.php +9 -0
  56. data/spec/fixtures/match_fullscan.php +9 -0
  57. data/spec/fixtures/match_phrase.php +9 -0
  58. data/spec/fixtures/max_query_time.php +9 -0
  59. data/spec/fixtures/miltiple_queries.php +12 -0
  60. data/spec/fixtures/ranking_bm25.php +9 -0
  61. data/spec/fixtures/ranking_fieldmask.php +9 -0
  62. data/spec/fixtures/ranking_matchany.php +9 -0
  63. data/spec/fixtures/ranking_none.php +9 -0
  64. data/spec/fixtures/ranking_proximity.php +9 -0
  65. data/spec/fixtures/ranking_proximity_bm25.php +9 -0
  66. data/spec/fixtures/ranking_wordcount.php +9 -0
  67. data/spec/fixtures/retries.php +9 -0
  68. data/spec/fixtures/retries_delay.php +9 -0
  69. data/spec/fixtures/select.php +9 -0
  70. data/spec/fixtures/set_override.php +11 -0
  71. data/spec/fixtures/sort_attr_asc.php +9 -0
  72. data/spec/fixtures/sort_attr_desc.php +9 -0
  73. data/spec/fixtures/sort_expr.php +9 -0
  74. data/spec/fixtures/sort_extended.php +9 -0
  75. data/spec/fixtures/sort_relevance.php +9 -0
  76. data/spec/fixtures/sort_time_segments.php +9 -0
  77. data/spec/fixtures/sphinxapi.php +1633 -0
  78. data/spec/fixtures/update_attributes.php +8 -0
  79. data/spec/fixtures/update_attributes_mva.php +8 -0
  80. data/spec/fixtures/weights.php +9 -0
  81. data/spec/spec_helper.rb +24 -0
  82. data/spec/sphinx/sphinx-id64.conf +67 -0
  83. data/spec/sphinx/sphinx.conf +67 -0
  84. data/spec/sphinx/sphinx_test.sql +88 -0
  85. data/sphinx.gemspec +127 -0
  86. 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