sphinx 0.9.10.2094 → 0.9.10.2122

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