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