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.
- data/README.rdoc +24 -4
- data/VERSION.yml +1 -1
- data/lib/sphinx.rb +28 -7
- data/lib/sphinx/client.rb +61 -223
- data/lib/sphinx/constants.rb +189 -0
- data/lib/sphinx/indifferent_access.rb +152 -0
- data/spec/client_response_spec.rb +4 -4
- data/spec/client_spec.rb +31 -21
- data/spec/client_validations_spec.rb +12 -3
- data/sphinx.gemspec +4 -2
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= Sphinx Client API
|
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::
|
141
|
+
sphinx.set_match_mode(Sphinx::SPH_MATCH_ANY)
|
142
142
|
|
143
|
-
Please note that these constants defined in a <tt>Sphinx
|
144
|
-
|
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:
|
data/VERSION.yml
CHANGED
data/lib/sphinx.rb
CHANGED
@@ -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-
|
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
|
-
|
25
|
-
require
|
26
|
-
require
|
27
|
-
require
|
28
|
-
require
|
29
|
-
require
|
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"
|
data/lib/sphinx/client.rb
CHANGED
@@ -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
|
-
|
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
|
37
|
+
# Log debug/info/warn to the given Logger, defaults to nil.
|
124
38
|
# @private
|
125
39
|
attr_reader :logger
|
126
40
|
|
127
|
-
|
128
|
-
#
|
129
|
-
|
130
|
-
|
131
|
-
#
|
132
|
-
|
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
|
-
|
462
|
-
|
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::
|
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::
|
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::
|
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
|
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
|
1109
|
-
raise ArgumentError, '"exclude" argument must be Boolean' unless
|
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.
|
1112
|
-
|
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 =
|
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
|
1948
|
-
|
1949
|
-
|
1950
|
-
|
1951
|
-
|
1952
|
-
|
1953
|
-
|
1954
|
-
|
1955
|
-
|
1956
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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::
|
147
|
-
'created_at' => Sphinx::
|
148
|
-
'rating' => Sphinx::
|
149
|
-
'tags' => Sphinx::
|
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
|
data/spec/client_spec.rb
CHANGED
@@ -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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
541
|
-
@sphinx.SetOverride('attr2', Sphinx::
|
542
|
-
@sphinx.SetOverride('attr3', Sphinx::
|
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::
|
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
|
-
}.
|
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
|
data/sphinx.gemspec
CHANGED
@@ -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.
|
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-
|
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.
|
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-
|
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
|