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.
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