sphinx 0.9.10.2094 → 0.9.10.2122

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- = Sphinx Client API 0.9.10
1
+ = Sphinx Client API
2
2
 
3
3
  This document gives an overview of what is Sphinx itself and how to use it
4
4
  from your Ruby on Rails application. For more information or documentation,
@@ -138,10 +138,10 @@ will be finally executed successfully.
138
138
  Most Sphinx API methods expecting for special constants will be passed.
139
139
  For example:
140
140
 
141
- sphinx.set_match_mode(Sphinx::Client::SPH_MATCH_ANY)
141
+ sphinx.set_match_mode(Sphinx::SPH_MATCH_ANY)
142
142
 
143
- Please note that these constants defined in a <tt>Sphinx::Client</tt>
144
- namespace. You can use symbols or strings instead of these awful
143
+ Please note that these constants defined in a <tt>Sphinx</tt>
144
+ module. You can use symbols or strings instead of these awful
145
145
  constants:
146
146
 
147
147
  sphinx.set_match_mode(:any)
@@ -195,6 +195,26 @@ save the order of records established by Sphinx.
195
195
  docs = posts.map(&:body)
196
196
  excerpts = sphinx.build_excerpts(docs, 'index', 'test')
197
197
 
198
+ == Logging
199
+
200
+ You can ask Sphinx client API to log it's activity to some log. In
201
+ order to do that you can pass a logger object into the <tt>Sphinx::Client</tt>
202
+ constructor:
203
+
204
+ require 'logger'
205
+ Sphinx::Client.new(Logger.new(STDOUT)).query('test')
206
+
207
+ Logger object should respond to methods :debug, :info, and :warn, and
208
+ accept blocks (this is what standard Ruby <tt>Logger</tt> class does).
209
+ Here is what you will see in your log:
210
+
211
+ * <tt>DEBUG</tt> -- <tt>query</tt>, <tt>add_query</tt>, <tt>run_queries</tt>
212
+ method calls with configured filters.
213
+ * <tt>INFO</tt> -- initialization with Sphinx version, servers change,
214
+ attempts to re-connect, and all attempts to do an API call with server
215
+ where request being performed.
216
+ * <tt>WARN</tt> -- various connection and socket errors.
217
+
198
218
  == Support
199
219
 
200
220
  Source code:
@@ -2,4 +2,4 @@
2
2
  :major: 0
3
3
  :minor: 9
4
4
  :patch: 10
5
- :build: 2094
5
+ :build: 2122
@@ -3,7 +3,7 @@
3
3
  # Author:: Dmytro Shteflyuk <mailto:kpumuk@kpumuk.info>.
4
4
  # Copyright:: Copyright (c) 2006 — 2009 Dmytro Shteflyuk
5
5
  # License:: Distributes under the same terms as Ruby
6
- # Version:: 0.9.10-r2091
6
+ # Version:: 0.9.10-r2122
7
7
  # Website:: http://kpumuk.info/projects/ror-plugins/sphinx
8
8
  # Sources:: http://github.com/kpumuk/sphinx
9
9
  #
@@ -15,15 +15,36 @@ module Sphinx
15
15
  config = YAML.load(File.read(File.dirname(__FILE__) + '/../VERSION.yml'))
16
16
  "#{config[:major]}.#{config[:minor]}.#{config[:patch]}.#{config[:build]}"
17
17
  end
18
+
19
+ # Base class for all Sphinx errors
20
+ class SphinxError < StandardError; end
21
+
22
+ # Connect error occurred on the API side.
23
+ class SphinxConnectError < SphinxError; end
24
+
25
+ # Request error occurred on the API side.
26
+ class SphinxResponseError < SphinxError; end
27
+
28
+ # Internal error occurred inside searchd.
29
+ class SphinxInternalError < SphinxError; end
30
+
31
+ # Temporary error occurred inside searchd.
32
+ class SphinxTemporaryError < SphinxError; end
33
+
34
+ # Unknown error occurred inside searchd.
35
+ class SphinxUnknownError < SphinxError; end
18
36
  end
19
37
 
20
38
  require 'net/protocol'
21
39
  require 'socket'
22
40
  require 'zlib'
23
41
 
24
- require File.dirname(__FILE__) + '/sphinx/request'
25
- require File.dirname(__FILE__) + '/sphinx/response'
26
- require File.dirname(__FILE__) + '/sphinx/timeout'
27
- require File.dirname(__FILE__) + '/sphinx/buffered_io'
28
- require File.dirname(__FILE__) + '/sphinx/server'
29
- require File.dirname(__FILE__) + '/sphinx/client'
42
+ path = File.dirname(__FILE__)
43
+ require "#{path}/sphinx/constants"
44
+ require "#{path}/sphinx/indifferent_access"
45
+ require "#{path}/sphinx/request"
46
+ require "#{path}/sphinx/response"
47
+ require "#{path}/sphinx/timeout"
48
+ require "#{path}/sphinx/buffered_io"
49
+ require "#{path}/sphinx/server"
50
+ require "#{path}/sphinx/client"
@@ -1,17 +1,4 @@
1
1
  module Sphinx
2
- # Base class for all Sphinx errors
3
- class SphinxError < StandardError; end
4
- # Connect error occurred on the API side.
5
- class SphinxConnectError < SphinxError; end
6
- # Request error occurred on the API side.
7
- class SphinxResponseError < SphinxError; end
8
- # Internal error occurred inside searchd.
9
- class SphinxInternalError < SphinxError; end
10
- # Temporary error occurred inside searchd.
11
- class SphinxTemporaryError < SphinxError; end
12
- # Unknown error occurred inside searchd.
13
- class SphinxUnknownError < SphinxError; end
14
-
15
2
  # The Sphinx Client API is used to communicate with <tt>searchd</tt>
16
3
  # daemon and perform requests.
17
4
  #
@@ -26,81 +13,8 @@ module Sphinx
26
13
  # excerpts = sphinx.build_excerpts(docs, 'index', 'test')
27
14
  #
28
15
  class Client
29
- #=================================================================
30
- # Known searchd commands
31
- #=================================================================
32
-
33
- # search command
34
- # @private
35
- SEARCHD_COMMAND_SEARCH = 0
36
- # excerpt command
37
- # @private
38
- SEARCHD_COMMAND_EXCERPT = 1
39
- # update command
40
- # @private
41
- SEARCHD_COMMAND_UPDATE = 2
42
- # keywords command
43
- # @private
44
- SEARCHD_COMMAND_KEYWORDS = 3
45
- # persist command
46
- # @private
47
- SEARCHD_COMMAND_PERSIST = 4
48
- # status command
49
- # @private
50
- SEARCHD_COMMAND_STATUS = 5
51
- # query command
52
- # @private
53
- SEARCHD_COMMAND_QUERY = 6
54
- # flushattrs command
55
- # @private
56
- SEARCHD_COMMAND_FLUSHATTRS = 7
57
-
58
- #=================================================================
59
- # Current client-side command implementation versions
60
- #=================================================================
61
-
62
- # search command version
63
- # @private
64
- VER_COMMAND_SEARCH = 0x117
65
- # excerpt command version
66
- # @private
67
- VER_COMMAND_EXCERPT = 0x100
68
- # update command version
69
- # @private
70
- VER_COMMAND_UPDATE = 0x102
71
- # keywords command version
72
- # @private
73
- VER_COMMAND_KEYWORDS = 0x100
74
- # persist command version
75
- # @private
76
- VER_COMMAND_PERSIST = 0x000
77
- # status command version
78
- # @private
79
- VER_COMMAND_STATUS = 0x100
80
- # query command version
81
- # @private
82
- VER_COMMAND_QUERY = 0x100
83
- # flushattrs command version
84
- # @private
85
- VER_COMMAND_FLUSHATTRS = 0x100
86
-
87
- #=================================================================
88
- # Known searchd status codes
89
- #=================================================================
90
-
91
- # general success, command-specific reply follows
92
- # @private
93
- SEARCHD_OK = 0
94
- # general failure, command-specific reply may follow
95
- # @private
96
- SEARCHD_ERROR = 1
97
- # temporaty failure, client should retry later
98
- # @private
99
- SEARCHD_RETRY = 2
100
- # general success, warning message and command-specific reply follow
101
- # @private
102
- SEARCHD_WARNING = 3
103
-
16
+ include Sphinx::Constants
17
+
104
18
  #=================================================================
105
19
  # Some internal attributes to use inside client API
106
20
  #=================================================================
@@ -120,118 +34,16 @@ module Sphinx
120
34
  # Number of request retries.
121
35
  # @private
122
36
  attr_reader :reqretries
123
- # Log debug/info/warn/error to the given Logger, defaults to nil.
37
+ # Log debug/info/warn to the given Logger, defaults to nil.
124
38
  # @private
125
39
  attr_reader :logger
126
40
 
127
- #=================================================================
128
- # Known match modes
129
- #=================================================================
130
-
131
- # match all query words
132
- SPH_MATCH_ALL = 0
133
- # match any query word
134
- SPH_MATCH_ANY = 1
135
- # match this exact phrase
136
- SPH_MATCH_PHRASE = 2
137
- # match this boolean query
138
- SPH_MATCH_BOOLEAN = 3
139
- # match this extended query
140
- SPH_MATCH_EXTENDED = 4
141
- # match all document IDs w/o fulltext query, apply filters
142
- SPH_MATCH_FULLSCAN = 5
143
- # extended engine V2 (TEMPORARY, WILL BE REMOVED IN 0.9.8-RELEASE)
144
- SPH_MATCH_EXTENDED2 = 6
145
-
146
- #=================================================================
147
- # Known ranking modes (ext2 only)
148
- #=================================================================
149
-
150
- # default mode, phrase proximity major factor and BM25 minor one
151
- SPH_RANK_PROXIMITY_BM25 = 0
152
- # statistical mode, BM25 ranking only (faster but worse quality)
153
- SPH_RANK_BM25 = 1
154
- # no ranking, all matches get a weight of 1
155
- SPH_RANK_NONE = 2
156
- # simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
157
- SPH_RANK_WORDCOUNT = 3
158
- # phrase proximity
159
- SPH_RANK_PROXIMITY = 4
160
- # emulate old match-any weighting
161
- SPH_RANK_MATCHANY = 5
162
- # sets bits where there were matches
163
- SPH_RANK_FIELDMASK = 6
164
- # codename SPH04, phrase proximity + bm25 + head/exact boost
165
- SPH_RANK_SPH04 = 7
166
-
167
- #=================================================================
168
- # Known sort modes
169
- #=================================================================
170
-
171
- # sort by document relevance desc, then by date
172
- SPH_SORT_RELEVANCE = 0
173
- # sort by document date desc, then by relevance desc
174
- SPH_SORT_ATTR_DESC = 1
175
- # sort by document date asc, then by relevance desc
176
- SPH_SORT_ATTR_ASC = 2
177
- # sort by time segments (hour/day/week/etc) desc, then by relevance desc
178
- SPH_SORT_TIME_SEGMENTS = 3
179
- # sort by SQL-like expression (eg. "@relevance DESC, price ASC, @id DESC")
180
- SPH_SORT_EXTENDED = 4
181
- # sort by arithmetic expression in descending order (eg. "@id + max(@weight,1000)*boost + log(price)")
182
- SPH_SORT_EXPR = 5
183
-
184
- #=================================================================
185
- # Known filter types
186
- #=================================================================
187
-
188
- # filter by integer values set
189
- SPH_FILTER_VALUES = 0
190
- # filter by integer range
191
- SPH_FILTER_RANGE = 1
192
- # filter by float range
193
- SPH_FILTER_FLOATRANGE = 2
194
-
195
- #=================================================================
196
- # Known attribute types
197
- #=================================================================
198
-
199
- # this attr is just an integer
200
- SPH_ATTR_INTEGER = 1
201
- # this attr is a timestamp
202
- SPH_ATTR_TIMESTAMP = 2
203
- # this attr is an ordinal string number (integer at search time,
204
- # specially handled at indexing time)
205
- SPH_ATTR_ORDINAL = 3
206
- # this attr is a boolean bit field
207
- SPH_ATTR_BOOL = 4
208
- # this attr is a float
209
- SPH_ATTR_FLOAT = 5
210
- # signed 64-bit integer
211
- SPH_ATTR_BIGINT = 6
212
- # string (binary; in-memory)
213
- SPH_ATTR_STRING = 7
214
- # this attr has multiple values (0 or more)
215
- SPH_ATTR_MULTI = 0x40000000
216
-
217
- #=================================================================
218
- # Known grouping functions
219
- #=================================================================
220
-
221
- # group by day
222
- SPH_GROUPBY_DAY = 0
223
- # group by week
224
- SPH_GROUPBY_WEEK = 1
225
- # group by month
226
- SPH_GROUPBY_MONTH = 2
227
- # group by year
228
- SPH_GROUPBY_YEAR = 3
229
- # group by attribute value
230
- SPH_GROUPBY_ATTR = 4
231
- # group by sequential attrs pair
232
- SPH_GROUPBY_ATTRPAIR = 5
233
-
234
- # Constructs the <tt>Sphinx::Client</tt> object and sets options to their default values.
41
+ # Constructs the <tt>Sphinx::Client</tt> object and sets options
42
+ # to their default values.
43
+ #
44
+ # @param [Logger] logger a logger object to put logs to. No logging
45
+ # will be performed when not set.
46
+ #
235
47
  def initialize(logger = nil)
236
48
  # per-query settings
237
49
  @offset = 0 # how many records to seek from result-set start (default is 0)
@@ -458,8 +270,10 @@ module Sphinx
458
270
  @servers = servers.map do |server|
459
271
  raise ArgumentError, '"servers" argument must be Array of Hashes' unless server.kind_of?(Hash)
460
272
 
461
- host = server[:path] || server['path'] || server[:host] || server['host']
462
- port = server[:port] || server['port'] || 9312
273
+ server = server.with_indifferent_access
274
+
275
+ host = server[:path] || server[:host]
276
+ port = server[:port] || 9312
463
277
  path = nil
464
278
  raise ArgumentError, '"host" argument must be String' unless host.kind_of?(String)
465
279
 
@@ -809,7 +623,7 @@ module Sphinx
809
623
  # @return [Sphinx::Client] self.
810
624
  #
811
625
  # @example
812
- # sphinx.set_match_mode(Sphinx::Client::SPH_MATCH_ALL)
626
+ # sphinx.set_match_mode(Sphinx::SPH_MATCH_ALL)
813
627
  # sphinx.set_match_mode(:all)
814
628
  # sphinx.set_match_mode('all')
815
629
  #
@@ -849,7 +663,7 @@ module Sphinx
849
663
  # @return [Sphinx::Client] self.
850
664
  #
851
665
  # @example
852
- # sphinx.set_ranking_mode(Sphinx::Client::SPH_RANK_BM25)
666
+ # sphinx.set_ranking_mode(Sphinx::SPH_RANK_BM25)
853
667
  # sphinx.set_ranking_mode(:bm25)
854
668
  # sphinx.set_ranking_mode('bm25')
855
669
  #
@@ -891,7 +705,7 @@ module Sphinx
891
705
  # @return [Sphinx::Client] self.
892
706
  #
893
707
  # @example
894
- # sphinx.set_sort_mode(Sphinx::Client::SPH_SORT_ATTR_ASC, 'attr')
708
+ # sphinx.set_sort_mode(Sphinx::SPH_SORT_ATTR_ASC, 'attr')
895
709
  # sphinx.set_sort_mode(:attr_asc, 'attr')
896
710
  # sphinx.set_sort_mode('attr_asc', 'attr')
897
711
  #
@@ -1087,7 +901,8 @@ module Sphinx
1087
901
  # be matched (or rejected, if +exclude+ is +true+).
1088
902
  #
1089
903
  # @param [String, Symbol] attribute an attribute name to filter by.
1090
- # @param [Array<Integer>] values an +Array+ of integers with given attribute values.
904
+ # @param [Array<Integer>, Integer] values an +Array+ of integers or
905
+ # single Integer with given attribute values.
1091
906
  # @param [Boolean] exclude indicating whether documents with given attribute
1092
907
  # matching specified values should be excluded from search results.
1093
908
  # @return [Sphinx::Client] self.
@@ -1104,15 +919,14 @@ module Sphinx
1104
919
  #
1105
920
  def set_filter(attribute, values, exclude = false)
1106
921
  raise ArgumentError, '"attribute" argument must be String or Symbol' unless attribute.kind_of?(String) or attribute.kind_of?(Symbol)
922
+ values = [values] if values.kind_of?(Integer)
1107
923
  raise ArgumentError, '"values" argument must be Array' unless values.kind_of?(Array)
1108
- raise ArgumentError, '"values" argument must not be empty' if values.empty?
1109
- raise ArgumentError, '"exclude" argument must be Boolean' unless exclude.kind_of?(TrueClass) or exclude.kind_of?(FalseClass)
924
+ raise ArgumentError, '"values" argument must be Array of Integers' unless values.all? { |v| v.kind_of?(Integer) }
925
+ raise ArgumentError, '"exclude" argument must be Boolean' unless [TrueClass, FalseClass].include?(exclude.class)
1110
926
 
1111
- values.each do |value|
1112
- raise ArgumentError, '"values" argument must be Array of Integer' unless value.kind_of?(Integer)
927
+ if values.any?
928
+ @filters << { 'type' => SPH_FILTER_VALUES, 'attr' => attribute.to_s, 'exclude' => exclude, 'values' => values }
1113
929
  end
1114
-
1115
- @filters << { 'type' => SPH_FILTER_VALUES, 'attr' => attribute.to_s, 'exclude' => exclude, 'values' => values }
1116
930
  self
1117
931
  end
1118
932
  alias :SetFilter :set_filter
@@ -1502,6 +1316,8 @@ module Sphinx
1502
1316
  # Query warning message reported by searchd (string, human readable).
1503
1317
  # Empty if there were no warnings.
1504
1318
  #
1319
+ # Please note: you can use both strings and symbols as <tt>Hash</tt> keys.
1320
+ #
1505
1321
  # It should be noted that {#query} carries out the same actions as
1506
1322
  # {#add_query} and {#run_queries} without the intermediate steps; it
1507
1323
  # is analoguous to a single {#add_query} call, followed by a
@@ -1799,7 +1615,7 @@ module Sphinx
1799
1615
 
1800
1616
  # parse response
1801
1617
  (1..nreqs).map do
1802
- result = { 'error' => '', 'warning' => '' }
1618
+ result = HashWithIndifferentAccess.new('error' => '', 'warning' => '')
1803
1619
 
1804
1620
  # extract status
1805
1621
  status = result['status'] = response.get_int
@@ -1944,16 +1760,18 @@ module Sphinx
1944
1760
  end
1945
1761
 
1946
1762
  # fixup options
1947
- opts['before_match'] ||= opts[:before_match] || '<b>';
1948
- opts['after_match'] ||= opts[:after_match] || '</b>';
1949
- opts['chunk_separator'] ||= opts[:chunk_separator] || ' ... ';
1950
- opts['limit'] ||= opts[:limit] || 256;
1951
- opts['around'] ||= opts[:around] || 5;
1952
- opts['exact_phrase'] ||= opts[:exact_phrase] || false
1953
- opts['single_passage'] ||= opts[:single_passage] || false
1954
- opts['use_boundaries'] ||= opts[:use_boundaries] || false
1955
- opts['weight_order'] ||= opts[:weight_order] || false
1956
- opts['query_mode'] ||= opts[:query_mode] || false
1763
+ opts = HashWithIndifferentAccess.new(
1764
+ 'before_match' => '<b>',
1765
+ 'after_match' => '</b>',
1766
+ 'chunk_separator' => ' ... ',
1767
+ 'limit' => 256,
1768
+ 'around' => 5,
1769
+ 'exact_phrase' => false,
1770
+ 'single_passage' => false,
1771
+ 'use_boundaries' => false,
1772
+ 'weight_order' => false,
1773
+ 'query_mode' => false
1774
+ ).update(opts)
1957
1775
 
1958
1776
  # build request
1959
1777
 
@@ -2044,7 +1862,7 @@ module Sphinx
2044
1862
  tokenized = response.get_string
2045
1863
  normalized = response.get_string
2046
1864
 
2047
- entry = { 'tokenized' => tokenized, 'normalized' => normalized }
1865
+ entry = HashWithIndifferentAccess.new('tokenized' => tokenized, 'normalized' => normalized)
2048
1866
  entry['docs'], entry['hits'] = response.get_ints(2) if hits
2049
1867
 
2050
1868
  entry
@@ -2154,6 +1972,26 @@ module Sphinx
2154
1972
  end
2155
1973
  alias :UpdateAttributes :update_attributes
2156
1974
 
1975
+ # Escapes characters that are treated as special operators by the
1976
+ # query language parser.
1977
+ #
1978
+ # This function might seem redundant because it's trivial to
1979
+ # implement in any calling application. However, as the set of
1980
+ # special characters might change over time, it makes sense to
1981
+ # have an API call that is guaranteed to escape all such
1982
+ # characters at all times.
1983
+ #
1984
+ # @param [String] string is a string to escape.
1985
+ # @return [String] an escaped string.
1986
+ #
1987
+ # @example:
1988
+ # escaped = sphinx.escape_string "escaping-sample@query/string"
1989
+ #
1990
+ def escape_string(string)
1991
+ string.to_s.gsub(/([\\()|\-!@~"&\/\^\$=])/, '\\\\\\1')
1992
+ end
1993
+ alias :EscapeString :escape_string
1994
+
2157
1995
  # Queries searchd status, and returns an array of status variable name
2158
1996
  # and value pairs.
2159
1997
  #
@@ -2194,11 +2032,11 @@ module Sphinx
2194
2032
  status = (0...rows).map do
2195
2033
  (0...cols).map { response.get_string }
2196
2034
  end
2197
- { :server => server.to_s, :status => status }
2035
+ HashWithIndifferentAccess.new(:server => server.to_s, :status => status)
2198
2036
  rescue SphinxError
2199
2037
  # Re-raise error when a single server configured
2200
2038
  raise if @servers.size == 1
2201
- { :server => server.to_s, :error => self.last_error}
2039
+ HashWithIndifferentAccess.new(:server => server.to_s, :error => self.last_error)
2202
2040
  end
2203
2041
  end
2204
2042
 
@@ -0,0 +1,189 @@
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
+ # flushattrs command
31
+ # @private
32
+ SEARCHD_COMMAND_FLUSHATTRS = 7
33
+
34
+ #=================================================================
35
+ # Current client-side command implementation versions
36
+ #=================================================================
37
+
38
+ # search command version
39
+ # @private
40
+ VER_COMMAND_SEARCH = 0x117
41
+ # excerpt command version
42
+ # @private
43
+ VER_COMMAND_EXCERPT = 0x100
44
+ # update command version
45
+ # @private
46
+ VER_COMMAND_UPDATE = 0x102
47
+ # keywords command version
48
+ # @private
49
+ VER_COMMAND_KEYWORDS = 0x100
50
+ # persist command version
51
+ # @private
52
+ VER_COMMAND_PERSIST = 0x000
53
+ # status command version
54
+ # @private
55
+ VER_COMMAND_STATUS = 0x100
56
+ # query command version
57
+ # @private
58
+ VER_COMMAND_QUERY = 0x100
59
+ # flushattrs command version
60
+ # @private
61
+ VER_COMMAND_FLUSHATTRS = 0x100
62
+
63
+ #=================================================================
64
+ # Known searchd status codes
65
+ #=================================================================
66
+
67
+ # general success, command-specific reply follows
68
+ # @private
69
+ SEARCHD_OK = 0
70
+ # general failure, command-specific reply may follow
71
+ # @private
72
+ SEARCHD_ERROR = 1
73
+ # temporaty failure, client should retry later
74
+ # @private
75
+ SEARCHD_RETRY = 2
76
+ # general success, warning message and command-specific reply follow
77
+ # @private
78
+ SEARCHD_WARNING = 3
79
+
80
+ #=================================================================
81
+ # Known match modes
82
+ #=================================================================
83
+
84
+ # match all query words
85
+ SPH_MATCH_ALL = 0
86
+ # match any query word
87
+ SPH_MATCH_ANY = 1
88
+ # match this exact phrase
89
+ SPH_MATCH_PHRASE = 2
90
+ # match this boolean query
91
+ SPH_MATCH_BOOLEAN = 3
92
+ # match this extended query
93
+ SPH_MATCH_EXTENDED = 4
94
+ # match all document IDs w/o fulltext query, apply filters
95
+ SPH_MATCH_FULLSCAN = 5
96
+ # extended engine V2 (TEMPORARY, WILL BE REMOVED IN 0.9.8-RELEASE)
97
+ SPH_MATCH_EXTENDED2 = 6
98
+
99
+ #=================================================================
100
+ # Known ranking modes (ext2 only)
101
+ #=================================================================
102
+
103
+ # default mode, phrase proximity major factor and BM25 minor one
104
+ SPH_RANK_PROXIMITY_BM25 = 0
105
+ # statistical mode, BM25 ranking only (faster but worse quality)
106
+ SPH_RANK_BM25 = 1
107
+ # no ranking, all matches get a weight of 1
108
+ SPH_RANK_NONE = 2
109
+ # simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
110
+ SPH_RANK_WORDCOUNT = 3
111
+ # phrase proximity
112
+ SPH_RANK_PROXIMITY = 4
113
+ # emulate old match-any weighting
114
+ SPH_RANK_MATCHANY = 5
115
+ # sets bits where there were matches
116
+ SPH_RANK_FIELDMASK = 6
117
+ # codename SPH04, phrase proximity + bm25 + head/exact boost
118
+ SPH_RANK_SPH04 = 7
119
+
120
+ #=================================================================
121
+ # Known sort modes
122
+ #=================================================================
123
+
124
+ # sort by document relevance desc, then by date
125
+ SPH_SORT_RELEVANCE = 0
126
+ # sort by document date desc, then by relevance desc
127
+ SPH_SORT_ATTR_DESC = 1
128
+ # sort by document date asc, then by relevance desc
129
+ SPH_SORT_ATTR_ASC = 2
130
+ # sort by time segments (hour/day/week/etc) desc, then by relevance desc
131
+ SPH_SORT_TIME_SEGMENTS = 3
132
+ # sort by SQL-like expression (eg. "@relevance DESC, price ASC, @id DESC")
133
+ SPH_SORT_EXTENDED = 4
134
+ # sort by arithmetic expression in descending order (eg. "@id + max(@weight,1000)*boost + log(price)")
135
+ SPH_SORT_EXPR = 5
136
+
137
+ #=================================================================
138
+ # Known filter types
139
+ #=================================================================
140
+
141
+ # filter by integer values set
142
+ SPH_FILTER_VALUES = 0
143
+ # filter by integer range
144
+ SPH_FILTER_RANGE = 1
145
+ # filter by float range
146
+ SPH_FILTER_FLOATRANGE = 2
147
+
148
+ #=================================================================
149
+ # Known attribute types
150
+ #=================================================================
151
+
152
+ # this attr is just an integer
153
+ SPH_ATTR_INTEGER = 1
154
+ # this attr is a timestamp
155
+ SPH_ATTR_TIMESTAMP = 2
156
+ # this attr is an ordinal string number (integer at search time,
157
+ # specially handled at indexing time)
158
+ SPH_ATTR_ORDINAL = 3
159
+ # this attr is a boolean bit field
160
+ SPH_ATTR_BOOL = 4
161
+ # this attr is a float
162
+ SPH_ATTR_FLOAT = 5
163
+ # signed 64-bit integer
164
+ SPH_ATTR_BIGINT = 6
165
+ # string (binary; in-memory)
166
+ SPH_ATTR_STRING = 7
167
+ # this attr has multiple values (0 or more)
168
+ SPH_ATTR_MULTI = 0x40000000
169
+
170
+ #=================================================================
171
+ # Known grouping functions
172
+ #=================================================================
173
+
174
+ # group by day
175
+ SPH_GROUPBY_DAY = 0
176
+ # group by week
177
+ SPH_GROUPBY_WEEK = 1
178
+ # group by month
179
+ SPH_GROUPBY_MONTH = 2
180
+ # group by year
181
+ SPH_GROUPBY_YEAR = 3
182
+ # group by attribute value
183
+ SPH_GROUPBY_ATTR = 4
184
+ # group by sequential attrs pair
185
+ SPH_GROUPBY_ATTRPAIR = 5
186
+ end
187
+
188
+ include Constants
189
+ 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
@@ -143,10 +143,10 @@ describe Sphinx::Client, 'connected' do
143
143
  result['matches'].length.should == 3
144
144
  result['time'].should_not be_nil
145
145
  result['attrs'].should == {
146
- 'group_id' => Sphinx::Client::SPH_ATTR_INTEGER,
147
- 'created_at' => Sphinx::Client::SPH_ATTR_TIMESTAMP,
148
- 'rating' => Sphinx::Client::SPH_ATTR_FLOAT,
149
- 'tags' => Sphinx::Client::SPH_ATTR_MULTI | Sphinx::Client::SPH_ATTR_INTEGER
146
+ 'group_id' => Sphinx::SPH_ATTR_INTEGER,
147
+ 'created_at' => Sphinx::SPH_ATTR_TIMESTAMP,
148
+ 'rating' => Sphinx::SPH_ATTR_FLOAT,
149
+ 'tags' => Sphinx::SPH_ATTR_MULTI | Sphinx::SPH_ATTR_INTEGER
150
150
  }
151
151
  result['fields'].should == [ 'name', 'description' ]
152
152
  result['total'].should == 3
@@ -15,7 +15,7 @@ describe Sphinx::Client, 'disconnected' do
15
15
  cnt.should == 1
16
16
  end
17
17
  end
18
-
18
+
19
19
  it 'should raise an exception on error' do
20
20
  2.times do
21
21
  cnt = 0
@@ -91,20 +91,20 @@ describe Sphinx::Client, 'disconnected' do
91
91
  end
92
92
  end
93
93
  end
94
-
94
+
95
95
  context 'in with_socket method' do
96
96
  before :each do
97
97
  @sphinx = Sphinx::Client.new
98
98
  @socket = mock('TCPSocket')
99
99
  end
100
-
100
+
101
101
  context 'without retries' do
102
102
  before :each do
103
103
  @server = mock('Server')
104
104
  @server.should_receive(:get_socket).and_yield(@socket).and_return(@socket)
105
105
  @server.should_receive(:free_socket).with(@socket).at_least(1)
106
106
  end
107
-
107
+
108
108
  it 'should initialize session' do
109
109
  @socket.should_receive(:write).with([1].pack('N'))
110
110
  @socket.should_receive(:read).with(4).and_return([1].pack('N'))
@@ -159,7 +159,7 @@ describe Sphinx::Client, 'disconnected' do
159
159
  @server.should_receive(:get_socket).at_least(1).times.and_yield(@socket).and_return(@socket)
160
160
  @server.should_receive(:free_socket).with(@socket).at_least(1)
161
161
  end
162
-
162
+
163
163
  it 'should raise an exception on error' do
164
164
  @socket.should_receive(:write).exactly(3).times.with([1].pack('N'))
165
165
  @socket.should_receive(:read).exactly(3).times.with(4).and_return([1].pack('N'))
@@ -174,7 +174,7 @@ describe Sphinx::Client, 'disconnected' do
174
174
  end
175
175
  end
176
176
  end
177
-
177
+
178
178
  context 'in parse_response method' do
179
179
  before :each do
180
180
  @sphinx = Sphinx::Client.new
@@ -182,20 +182,20 @@ describe Sphinx::Client, 'disconnected' do
182
182
  end
183
183
 
184
184
  it 'should receive response' do
185
- @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 4].pack('n2N'))
185
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_OK, 1, 4].pack('n2N'))
186
186
  @socket.should_receive(:read).with(4).and_return([0].pack('N'))
187
187
  @sphinx.send(:parse_response, @socket, 1)
188
188
  end
189
189
 
190
190
  it 'should raise exception on zero-sized response' do
191
- @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 0].pack('n2N'))
191
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_OK, 1, 0].pack('n2N'))
192
192
  expect {
193
193
  @sphinx.send(:parse_response, @socket, 1)
194
194
  }.to raise_error(Sphinx::SphinxResponseError, 'received zero-sized searchd response')
195
195
  end
196
196
 
197
197
  it 'should raise exception when response is incomplete' do
198
- @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 4].pack('n2N'))
198
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_OK, 1, 4].pack('n2N'))
199
199
  @socket.should_receive(:read).with(4).and_return('')
200
200
  expect {
201
201
  @sphinx.send(:parse_response, @socket, 1)
@@ -203,14 +203,14 @@ describe Sphinx::Client, 'disconnected' do
203
203
  end
204
204
 
205
205
  it 'should set warning message when SEARCHD_WARNING received' do
206
- @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_WARNING, 1, 14].pack('n2N'))
206
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_WARNING, 1, 14].pack('n2N'))
207
207
  @socket.should_receive(:read).with(14).and_return([5].pack('N') + 'helloworld')
208
208
  @sphinx.send(:parse_response, @socket, 1).should == 'world'
209
209
  @sphinx.GetLastWarning.should == 'hello'
210
210
  end
211
211
 
212
212
  it 'should raise exception when SEARCHD_ERROR received' do
213
- @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_ERROR, 1, 9].pack('n2N'))
213
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_ERROR, 1, 9].pack('n2N'))
214
214
  @socket.should_receive(:read).with(9).and_return([1].pack('N') + 'hello')
215
215
  expect {
216
216
  @sphinx.send(:parse_response, @socket, 1)
@@ -218,7 +218,7 @@ describe Sphinx::Client, 'disconnected' do
218
218
  end
219
219
 
220
220
  it 'should raise exception when SEARCHD_RETRY received' do
221
- @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_RETRY, 1, 9].pack('n2N'))
221
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_RETRY, 1, 9].pack('n2N'))
222
222
  @socket.should_receive(:read).with(9).and_return([1].pack('N') + 'hello')
223
223
  expect {
224
224
  @sphinx.send(:parse_response, @socket, 1)
@@ -234,7 +234,7 @@ describe Sphinx::Client, 'disconnected' do
234
234
  end
235
235
 
236
236
  it 'should set warning when server is older than client' do
237
- @socket.should_receive(:read).with(8).and_return([Sphinx::Client::SEARCHD_OK, 1, 9].pack('n2N'))
237
+ @socket.should_receive(:read).with(8).and_return([Sphinx::SEARCHD_OK, 1, 9].pack('n2N'))
238
238
  @socket.should_receive(:read).with(9).and_return([1].pack('N') + 'hello')
239
239
  @sphinx.send(:parse_response, @socket, 5)
240
240
  @sphinx.GetLastWarning.should == 'searchd command v.0.1 older than client\'s v.0.5, some options might not work'
@@ -291,7 +291,7 @@ describe Sphinx::Client, 'disconnected' do
291
291
  it "should generate valid request for SPH_MATCH_#{match.to_s.upcase}" do
292
292
  expected = sphinx_fixture("match_#{match}")
293
293
  @sock.should_receive(:write).with(expected)
294
- @sphinx.SetMatchMode(Sphinx::Client::const_get("SPH_MATCH_#{match.to_s.upcase}"))
294
+ @sphinx.SetMatchMode(Sphinx::const_get("SPH_MATCH_#{match.to_s.upcase}"))
295
295
  sphinx_safe_call { @sphinx.Query('query') }
296
296
  end
297
297
 
@@ -485,7 +485,7 @@ describe Sphinx::Client, 'disconnected' do
485
485
  it "should generate valid request for SPH_GROUPBY_#{groupby.to_s.upcase}" do
486
486
  expected = sphinx_fixture("group_by_#{groupby}")
487
487
  @sock.should_receive(:write).with(expected)
488
- @sphinx.SetGroupBy('attr', Sphinx::Client::const_get("SPH_GROUPBY_#{groupby.to_s.upcase}"))
488
+ @sphinx.SetGroupBy('attr', Sphinx::const_get("SPH_GROUPBY_#{groupby.to_s.upcase}"))
489
489
  sphinx_safe_call { @sphinx.Query('query') }
490
490
  end
491
491
 
@@ -507,14 +507,14 @@ describe Sphinx::Client, 'disconnected' do
507
507
  it 'should generate valid request for SPH_GROUPBY_DAY with sort' do
508
508
  expected = sphinx_fixture('group_by_day_sort')
509
509
  @sock.should_receive(:write).with(expected)
510
- @sphinx.SetGroupBy('attr', Sphinx::Client::SPH_GROUPBY_DAY, 'somesort')
510
+ @sphinx.SetGroupBy('attr', Sphinx::SPH_GROUPBY_DAY, 'somesort')
511
511
  sphinx_safe_call { @sphinx.Query('query') }
512
512
  end
513
513
 
514
514
  it 'should generate valid request with count-distinct attribute' do
515
515
  expected = sphinx_fixture('group_distinct')
516
516
  @sock.should_receive(:write).with(expected)
517
- @sphinx.SetGroupBy('attr', Sphinx::Client::SPH_GROUPBY_DAY)
517
+ @sphinx.SetGroupBy('attr', Sphinx::SPH_GROUPBY_DAY)
518
518
  @sphinx.SetGroupDistinct('attr')
519
519
  sphinx_safe_call { @sphinx.Query('query') }
520
520
  end
@@ -537,9 +537,9 @@ describe Sphinx::Client, 'disconnected' do
537
537
  it 'should generate valid request for SetOverride' do
538
538
  expected = sphinx_fixture('set_override')
539
539
  @sock.should_receive(:write).with(expected)
540
- @sphinx.SetOverride('attr1', Sphinx::Client::SPH_ATTR_INTEGER, { 10 => 20 })
541
- @sphinx.SetOverride('attr2', Sphinx::Client::SPH_ATTR_FLOAT, { 11 => 30.3 })
542
- @sphinx.SetOverride('attr3', Sphinx::Client::SPH_ATTR_BIGINT, { 12 => 1099511627780 })
540
+ @sphinx.SetOverride('attr1', Sphinx::SPH_ATTR_INTEGER, { 10 => 20 })
541
+ @sphinx.SetOverride('attr2', Sphinx::SPH_ATTR_FLOAT, { 11 => 30.3 })
542
+ @sphinx.SetOverride('attr3', Sphinx::SPH_ATTR_BIGINT, { 12 => 1099511627780 })
543
543
  sphinx_safe_call { @sphinx.Query('query') }
544
544
  end
545
545
 
@@ -562,7 +562,7 @@ describe Sphinx::Client, 'disconnected' do
562
562
 
563
563
  @sphinx.SetRetries(10, 20)
564
564
  @sphinx.AddQuery('test1')
565
- @sphinx.SetGroupBy('attr', Sphinx::Client::SPH_GROUPBY_DAY)
565
+ @sphinx.SetGroupBy('attr', Sphinx::SPH_GROUPBY_DAY)
566
566
  @sphinx.AddQuery('test2')
567
567
 
568
568
  sphinx_safe_call { @sphinx.RunQueries }
@@ -656,4 +656,14 @@ describe Sphinx::Client, 'disconnected' do
656
656
  sphinx_safe_call { @sphinx.UpdateAttributes('index', ['group', 'category'], { 123 => [ [456, 789], [1, 2, 3] ] }, true) }
657
657
  end
658
658
  end
659
+
660
+ context 'in EscapeString method' do
661
+ before :each do
662
+ @sphinx = Sphinx::Client.new
663
+ end
664
+
665
+ it 'should escape special characters' do
666
+ @sphinx.escape_string("escaping-sample@query/string").should == "escaping\\-sample\\@query\\/string"
667
+ end
668
+ end
659
669
  end
@@ -327,10 +327,18 @@ describe Sphinx::Client, 'disconnected' do
327
327
  }.to_not raise_error(ArgumentError)
328
328
  end
329
329
 
330
- it 'should raise an error when values is not Array' do
330
+ it 'should raise an error when values is not Array or Integer' do
331
331
  expect {
332
332
  @sphinx.SetFilter(:attr, {})
333
333
  }.to raise_error(ArgumentError)
334
+
335
+ expect {
336
+ @sphinx.SetFilter(:attr, 1)
337
+ }.to_not raise_error(ArgumentError)
338
+
339
+ expect {
340
+ @sphinx.SetFilter(:attr, [1])
341
+ }.to_not raise_error(ArgumentError)
334
342
  end
335
343
 
336
344
  it 'should raise an error when values is not Array of Integers' do
@@ -339,10 +347,11 @@ describe Sphinx::Client, 'disconnected' do
339
347
  }.to raise_error(ArgumentError)
340
348
  end
341
349
 
342
- it 'should raise an error when values is empty Array' do
350
+ it 'should not raise an error when values is empty Array' do
343
351
  expect {
344
352
  @sphinx.SetFilter(:attr, [])
345
- }.to raise_error(ArgumentError)
353
+ }.to_not raise_error(ArgumentError)
354
+ @sphinx.instance_variable_get(:@filters).should be_empty
346
355
  end
347
356
 
348
357
  it 'should raise an error when exclude is not Boolean' do
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{sphinx}
8
- s.version = "0.9.10.2094"
8
+ s.version = "0.9.10.2122"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Dmytro Shteflyuk"]
12
- s.date = %q{2009-11-23}
12
+ s.date = %q{2009-12-04}
13
13
  s.description = %q{An easy interface to Sphinx standalone full-text search engine. It is implemented as plugin for Ruby on Rails, but can be easily used as standalone library.}
14
14
  s.email = %q{kpumuk@kpumuk.info}
15
15
  s.extra_rdoc_files = [
@@ -24,6 +24,8 @@ Gem::Specification.new do |s|
24
24
  "lib/sphinx.rb",
25
25
  "lib/sphinx/buffered_io.rb",
26
26
  "lib/sphinx/client.rb",
27
+ "lib/sphinx/constants.rb",
28
+ "lib/sphinx/indifferent_access.rb",
27
29
  "lib/sphinx/request.rb",
28
30
  "lib/sphinx/response.rb",
29
31
  "lib/sphinx/server.rb",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sphinx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.10.2094
4
+ version: 0.9.10.2122
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmytro Shteflyuk
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-23 00:00:00 +02:00
12
+ date: 2009-12-04 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -30,6 +30,8 @@ files:
30
30
  - lib/sphinx.rb
31
31
  - lib/sphinx/buffered_io.rb
32
32
  - lib/sphinx/client.rb
33
+ - lib/sphinx/constants.rb
34
+ - lib/sphinx/indifferent_access.rb
33
35
  - lib/sphinx/request.rb
34
36
  - lib/sphinx/response.rb
35
37
  - lib/sphinx/server.rb