sphinx 0.9.10.2122 → 2.1.1.3711

Sign up to get free protection for your applications and to get access to all the features.
Files changed (185) hide show
  1. data/.gitignore +5 -2
  2. data/.travis.yml +8 -0
  3. data/CHANGELOG.md +71 -0
  4. data/Gemfile +4 -0
  5. data/README.md +221 -0
  6. data/Rakefile +40 -35
  7. data/lib/sphinx.rb +3 -8
  8. data/lib/sphinx/client.rb +223 -70
  9. data/lib/sphinx/constants.rb +23 -8
  10. data/lib/sphinx/response.rb +6 -6
  11. data/lib/sphinx/timeout.rb +1 -2
  12. data/lib/sphinx/version.rb +3 -0
  13. data/spec/client_response_spec.rb +77 -64
  14. data/spec/client_spec.rb +68 -31
  15. data/spec/client_validations_spec.rb +61 -7
  16. data/spec/fixtures/requests/default_search.dat +0 -0
  17. data/spec/fixtures/requests/default_search_index.dat +0 -0
  18. data/spec/fixtures/requests/excerpt_custom.dat +0 -0
  19. data/spec/fixtures/requests/excerpt_default.dat +0 -0
  20. data/spec/fixtures/requests/excerpt_flags.dat +0 -0
  21. data/spec/fixtures/requests/field_weights.dat +0 -0
  22. data/spec/fixtures/requests/filter.dat +0 -0
  23. data/spec/fixtures/requests/filter_exclude.dat +0 -0
  24. data/spec/fixtures/requests/filter_float_range.dat +0 -0
  25. data/spec/fixtures/requests/filter_float_range_exclude.dat +0 -0
  26. data/spec/fixtures/requests/filter_range.dat +0 -0
  27. data/spec/fixtures/requests/filter_range_exclude.dat +0 -0
  28. data/spec/fixtures/requests/filter_range_int64.dat +0 -0
  29. data/spec/fixtures/requests/filter_ranges.dat +0 -0
  30. data/spec/fixtures/requests/filters.dat +0 -0
  31. data/spec/fixtures/requests/filters_different.dat +0 -0
  32. data/spec/fixtures/requests/geo_anchor.dat +0 -0
  33. data/spec/fixtures/requests/group_by_attr.dat +0 -0
  34. data/spec/fixtures/requests/group_by_attrpair.dat +0 -0
  35. data/spec/fixtures/requests/group_by_day.dat +0 -0
  36. data/spec/fixtures/requests/group_by_day_sort.dat +0 -0
  37. data/spec/fixtures/requests/group_by_month.dat +0 -0
  38. data/spec/fixtures/requests/group_by_week.dat +0 -0
  39. data/spec/fixtures/requests/group_by_year.dat +0 -0
  40. data/spec/fixtures/requests/group_distinct.dat +0 -0
  41. data/spec/fixtures/requests/id_range.dat +0 -0
  42. data/spec/fixtures/requests/id_range64.dat +0 -0
  43. data/spec/fixtures/requests/index_weights.dat +0 -0
  44. data/spec/fixtures/requests/keywords.dat +0 -0
  45. data/spec/fixtures/requests/limits.dat +0 -0
  46. data/spec/fixtures/requests/limits_cutoff.dat +0 -0
  47. data/spec/fixtures/requests/limits_max.dat +0 -0
  48. data/spec/fixtures/requests/limits_max_cutoff.dat +0 -0
  49. data/spec/fixtures/requests/match_all.dat +0 -0
  50. data/spec/fixtures/requests/match_any.dat +0 -0
  51. data/spec/fixtures/requests/match_boolean.dat +0 -0
  52. data/spec/fixtures/requests/match_extended.dat +0 -0
  53. data/spec/fixtures/requests/match_extended2.dat +0 -0
  54. data/spec/fixtures/requests/match_fullscan.dat +0 -0
  55. data/spec/fixtures/requests/match_phrase.dat +0 -0
  56. data/spec/fixtures/requests/max_query_time.dat +0 -0
  57. data/spec/fixtures/requests/miltiple_queries.dat +0 -0
  58. data/spec/fixtures/requests/outer_select.dat +0 -0
  59. data/spec/fixtures/requests/override.dat +0 -0
  60. data/spec/fixtures/{default_search.php → requests/php/default_search.php} +1 -1
  61. data/spec/fixtures/{default_search_index.php → requests/php/default_search_index.php} +1 -1
  62. data/spec/fixtures/{excerpt_custom.php → requests/php/excerpt_custom.php} +1 -1
  63. data/spec/fixtures/{excerpt_default.php → requests/php/excerpt_default.php} +1 -1
  64. data/spec/fixtures/{excerpt_flags.php → requests/php/excerpt_flags.php} +1 -1
  65. data/spec/fixtures/{field_weights.php → requests/php/field_weights.php} +1 -1
  66. data/spec/fixtures/{filter.php → requests/php/filter.php} +1 -1
  67. data/spec/fixtures/{filter_exclude.php → requests/php/filter_exclude.php} +1 -1
  68. data/spec/fixtures/{filter_float_range.php → requests/php/filter_float_range.php} +1 -1
  69. data/spec/fixtures/{filter_float_range_exclude.php → requests/php/filter_float_range_exclude.php} +1 -1
  70. data/spec/fixtures/{filter_range.php → requests/php/filter_range.php} +1 -1
  71. data/spec/fixtures/{filter_range_exclude.php → requests/php/filter_range_exclude.php} +1 -1
  72. data/spec/fixtures/{filter_range_int64.php → requests/php/filter_range_int64.php} +1 -1
  73. data/spec/fixtures/{filter_ranges.php → requests/php/filter_ranges.php} +1 -1
  74. data/spec/fixtures/{filters.php → requests/php/filters.php} +1 -1
  75. data/spec/fixtures/{filters_different.php → requests/php/filters_different.php} +1 -1
  76. data/spec/fixtures/{geo_anchor.php → requests/php/geo_anchor.php} +1 -1
  77. data/spec/fixtures/{group_by_attr.php → requests/php/group_by_attr.php} +1 -1
  78. data/spec/fixtures/{group_by_attrpair.php → requests/php/group_by_attrpair.php} +1 -1
  79. data/spec/fixtures/{group_by_day.php → requests/php/group_by_day.php} +1 -1
  80. data/spec/fixtures/{group_by_day_sort.php → requests/php/group_by_day_sort.php} +1 -1
  81. data/spec/fixtures/{group_by_month.php → requests/php/group_by_month.php} +1 -1
  82. data/spec/fixtures/{group_by_week.php → requests/php/group_by_week.php} +1 -1
  83. data/spec/fixtures/{group_by_year.php → requests/php/group_by_year.php} +1 -1
  84. data/spec/fixtures/{group_distinct.php → requests/php/group_distinct.php} +1 -1
  85. data/spec/fixtures/{id_range.php → requests/php/id_range.php} +1 -1
  86. data/spec/fixtures/{id_range64.php → requests/php/id_range64.php} +1 -1
  87. data/spec/fixtures/{index_weights.php → requests/php/index_weights.php} +1 -1
  88. data/spec/fixtures/{keywords.php → requests/php/keywords.php} +1 -1
  89. data/spec/fixtures/{limits.php → requests/php/limits.php} +1 -1
  90. data/spec/fixtures/{limits_cutoff.php → requests/php/limits_cutoff.php} +1 -1
  91. data/spec/fixtures/{limits_max.php → requests/php/limits_max.php} +1 -1
  92. data/spec/fixtures/{limits_max_cutoff.php → requests/php/limits_max_cutoff.php} +1 -1
  93. data/spec/fixtures/{match_all.php → requests/php/match_all.php} +1 -1
  94. data/spec/fixtures/{match_any.php → requests/php/match_any.php} +1 -1
  95. data/spec/fixtures/{match_boolean.php → requests/php/match_boolean.php} +1 -1
  96. data/spec/fixtures/{match_extended.php → requests/php/match_extended.php} +1 -1
  97. data/spec/fixtures/{match_extended2.php → requests/php/match_extended2.php} +1 -1
  98. data/spec/fixtures/{match_fullscan.php → requests/php/match_fullscan.php} +1 -1
  99. data/spec/fixtures/{match_phrase.php → requests/php/match_phrase.php} +1 -1
  100. data/spec/fixtures/{max_query_time.php → requests/php/max_query_time.php} +1 -1
  101. data/spec/fixtures/{miltiple_queries.php → requests/php/miltiple_queries.php} +1 -1
  102. data/spec/fixtures/requests/php/outer_select.php +9 -0
  103. data/spec/fixtures/{set_override.php → requests/php/override.php} +1 -1
  104. data/spec/fixtures/requests/php/query_flag.php +13 -0
  105. data/spec/fixtures/requests/php/query_flag_after_reset.php +19 -0
  106. data/spec/fixtures/{ranking_bm25.php → requests/php/ranking_bm25.php} +1 -1
  107. data/spec/fixtures/requests/php/ranking_expr.php +9 -0
  108. data/spec/fixtures/{ranking_fieldmask.php → requests/php/ranking_fieldmask.php} +1 -1
  109. data/spec/fixtures/{ranking_matchany.php → requests/php/ranking_matchany.php} +1 -1
  110. data/spec/fixtures/{ranking_none.php → requests/php/ranking_none.php} +1 -1
  111. data/spec/fixtures/{ranking_proximity.php → requests/php/ranking_proximity.php} +1 -1
  112. data/spec/fixtures/{ranking_proximity_bm25.php → requests/php/ranking_proximity_bm25.php} +1 -1
  113. data/spec/fixtures/{ranking_sph04.php → requests/php/ranking_sph04.php} +1 -1
  114. data/spec/fixtures/{ranking_wordcount.php → requests/php/ranking_wordcount.php} +1 -1
  115. data/spec/fixtures/{retries.php → requests/php/retries.php} +1 -1
  116. data/spec/fixtures/{retries_delay.php → requests/php/retries_delay.php} +1 -1
  117. data/spec/fixtures/{select.php → requests/php/select.php} +1 -1
  118. data/spec/fixtures/{sort_attr_asc.php → requests/php/sort_attr_asc.php} +1 -1
  119. data/spec/fixtures/{sort_attr_desc.php → requests/php/sort_attr_desc.php} +1 -1
  120. data/spec/fixtures/{sort_expr.php → requests/php/sort_expr.php} +1 -1
  121. data/spec/fixtures/{sort_extended.php → requests/php/sort_extended.php} +1 -1
  122. data/spec/fixtures/{sort_relevance.php → requests/php/sort_relevance.php} +1 -1
  123. data/spec/fixtures/{sort_time_segments.php → requests/php/sort_time_segments.php} +1 -1
  124. data/spec/fixtures/{update_attributes.php → requests/php/update_attributes.php} +1 -1
  125. data/spec/fixtures/{update_attributes_mva.php → requests/php/update_attributes_mva.php} +1 -1
  126. data/spec/fixtures/{weights.php → requests/php/weights.php} +1 -1
  127. data/spec/fixtures/requests/query_flag.dat +0 -0
  128. data/spec/fixtures/requests/query_flag_after_reset.dat +0 -0
  129. data/spec/fixtures/requests/ranking_bm25.dat +0 -0
  130. data/spec/fixtures/requests/ranking_expr.dat +0 -0
  131. data/spec/fixtures/requests/ranking_fieldmask.dat +0 -0
  132. data/spec/fixtures/requests/ranking_matchany.dat +0 -0
  133. data/spec/fixtures/requests/ranking_none.dat +0 -0
  134. data/spec/fixtures/requests/ranking_proximity.dat +0 -0
  135. data/spec/fixtures/requests/ranking_proximity_bm25.dat +0 -0
  136. data/spec/fixtures/requests/ranking_sph04.dat +0 -0
  137. data/spec/fixtures/requests/ranking_wordcount.dat +0 -0
  138. data/spec/fixtures/requests/retries.dat +0 -0
  139. data/spec/fixtures/requests/retries_delay.dat +0 -0
  140. data/spec/fixtures/requests/select.dat +0 -0
  141. data/spec/fixtures/requests/sort_attr_asc.dat +0 -0
  142. data/spec/fixtures/requests/sort_attr_desc.dat +0 -0
  143. data/spec/fixtures/requests/sort_expr.dat +0 -0
  144. data/spec/fixtures/requests/sort_extended.dat +0 -0
  145. data/spec/fixtures/requests/sort_relevance.dat +0 -0
  146. data/spec/fixtures/requests/sort_time_segments.dat +0 -0
  147. data/spec/fixtures/requests/update_attributes.dat +0 -0
  148. data/spec/fixtures/requests/update_attributes_mva.dat +0 -0
  149. data/spec/fixtures/requests/weights.dat +0 -0
  150. data/spec/fixtures/responses/build_excerpts.dat +0 -0
  151. data/spec/fixtures/responses/build_keywords.dat +0 -0
  152. data/spec/fixtures/responses/flush_attributes.dat +0 -0
  153. data/spec/fixtures/responses/flush_attrs.dat +2 -0
  154. data/spec/fixtures/responses/open.dat +0 -0
  155. data/spec/fixtures/responses/open_twice.dat +0 -0
  156. data/spec/fixtures/responses/php/build_excerpts.php +8 -0
  157. data/spec/fixtures/responses/php/build_keywords.php +8 -0
  158. data/spec/fixtures/responses/php/flush_attributes.php +8 -0
  159. data/spec/fixtures/responses/php/open.php +8 -0
  160. data/spec/fixtures/responses/php/open_twice.php +9 -0
  161. data/spec/fixtures/responses/php/query.php +8 -0
  162. data/spec/fixtures/responses/php/query_error.php +8 -0
  163. data/spec/fixtures/responses/php/query_id64.php +8 -0
  164. data/spec/fixtures/responses/php/run_queries.php +10 -0
  165. data/spec/fixtures/responses/php/run_queries_error.php +9 -0
  166. data/spec/fixtures/responses/php/status.php +8 -0
  167. data/spec/fixtures/responses/php/update_attributes.php +8 -0
  168. data/spec/fixtures/responses/php/update_attributes_mva.php +8 -0
  169. data/spec/fixtures/responses/query.dat +0 -0
  170. data/spec/fixtures/responses/query_error.dat +0 -0
  171. data/spec/fixtures/responses/query_id64.dat +0 -0
  172. data/spec/fixtures/responses/run_queries.dat +0 -0
  173. data/spec/fixtures/responses/run_queries_error.dat +0 -0
  174. data/spec/fixtures/responses/status.dat +0 -0
  175. data/spec/fixtures/responses/update_attributes.dat +0 -0
  176. data/spec/fixtures/responses/update_attributes_mva.dat +0 -0
  177. data/spec/fixtures/sphinxapi.php +217 -45
  178. data/spec/spec_helper.rb +18 -3
  179. data/spec/sphinx/sphinx-id64.conf +6 -6
  180. data/spec/sphinx/sphinx.conf +6 -6
  181. data/sphinx.gemspec +19 -121
  182. metadata +268 -105
  183. data/README.rdoc +0 -243
  184. data/VERSION.yml +0 -5
  185. data/init.rb +0 -1
data/.gitignore CHANGED
@@ -1,4 +1,7 @@
1
- rdoc
2
- doc
3
1
  .yardoc
2
+ .DS_Store
3
+ .bundle
4
+ .rvmrc
5
+ Gemfile.lock
6
+ doc
4
7
  pkg
@@ -0,0 +1,8 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - ree
5
+
6
+ notifications:
7
+ recipients:
8
+ - kpumuk@kpumuk.info
@@ -0,0 +1,71 @@
1
+ ## 2.1.1.3711 (Mar 15, 2013)
2
+
3
+ Features:
4
+
5
+ - Updated API to the latest Sphinx version 2.1.1.
6
+ - Refactored specs so they don't require running Sphinx instance and php.
7
+
8
+ ## 0.9.10.2122 (Dec 04, 2009)
9
+
10
+ Features:
11
+
12
+ - Sphinx::Client#escape_string method added.
13
+
14
+ Bugfixes:
15
+
16
+ - Allow empty array or single integer in #set_filter as values.
17
+
18
+ ## 0.9.10.2094 (Nov 23, 2009)
19
+
20
+ Features:
21
+
22
+ - Added logging.
23
+ - Added ability to pass a block to Client#query method to set request parameters.
24
+ - Use CRC32 of the request to select the server.
25
+ - Results returned in an instance of HashWithIndifferentAccess.
26
+
27
+ ## 0.9.10.2091 (Nov 20, 2009)
28
+
29
+ Features:
30
+
31
+ - Added Ruby-style named methods in addition to native Sphinx API naming.
32
+ - Return `Sphinx::Client` object itself from any `set_` method to allow chaining.
33
+ - Status() API call queries all configured servers.
34
+
35
+ ## 0.9.10.2086 (Nov 19, 2009)
36
+
37
+ Bugfixes:
38
+
39
+ - Better documentation.
40
+ - Fixed incomplete reply handling.
41
+ - Sphinx IANA assigned ports are 9312 and 9306 respectively (goodbye, trusty 3312)
42
+
43
+ ## 0.9.10.2043 (Nov 16, 2009)
44
+
45
+ Features:
46
+
47
+ - Updated Sphinx API to version 0.9.10.
48
+ - Added ability to set multiple servers.
49
+
50
+ Bugfixes:
51
+
52
+ - Better argument validation.
53
+ - Properly handle connection timeouts.
54
+ - Added request timeout handling and retries.
55
+ - Close TCP socket on connection failure.
56
+
57
+ ## 0.5.0.1112 (Aug 4, 2008)
58
+
59
+ Features:
60
+
61
+ - Updated Sphinx API to version 0.9.9.
62
+
63
+ Bugfixes:
64
+
65
+ - Fixed support of 64-bit values.
66
+
67
+ ## 0.4.0.1112 (May 2, 2008)
68
+
69
+ Features:
70
+
71
+ - Initial implementation of Sphinx API for Sphinx 0.9.8.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in meta-tags.gemspec
4
+ gemspec
@@ -0,0 +1,221 @@
1
+ # Sphinx Client API
2
+
3
+ [![Travis-CI build status](https://secure.travis-ci.org/kpumuk/sphinx.png)](http://travis-ci.org/kpumuk/sphinx)
4
+
5
+ This document gives an overview of what is Sphinx itself and how to use it
6
+ from your Ruby on Rails application. For more information about Sphinx and
7
+ its API documentation visit [sphinxsearch.com](http://www.sphinxsearch.com).
8
+
9
+ ## Sphinx
10
+
11
+ Sphinx is a standalone full-text search engine, meant to provide fast,
12
+ size-efficient and relevant fulltext search functions to other applications.
13
+ Sphinx was specially designed to integrate well with SQL databases and
14
+ scripting languages. Currently built-in data sources support fetching data
15
+ either via direct connection to MySQL, or from an XML pipe.
16
+
17
+ Simplest way to communicate with Sphinx is to use `searchd` —
18
+ a daemon to search through full text indexes from external software.
19
+
20
+ ## Installation
21
+
22
+ Add the "sphinx" gem to your `Gemfile`.
23
+
24
+ gem 'sphinx'
25
+
26
+ And run `bundle install` command.
27
+
28
+ ## Documentation
29
+
30
+ Complete Sphinx plugin documentation could be found on [GitHub Pages](http://kpumuk.github.com/sphinx).
31
+
32
+ Also you can find documentation on [rdoc.info](http://rdoc.info/projects/kpumuk/sphinx).
33
+
34
+ You can build the documentation locally by running:
35
+
36
+ rake yard
37
+
38
+ Complete Sphinx API documentation could be found on [Sphinx Search Engine
39
+ site](http://www.sphinxsearch.com/docs/current.html).
40
+ This plugin is fully compatible with original PHP API implementation.
41
+
42
+ ## Ruby naming conventions
43
+
44
+ Sphinx Client API supports Ruby naming conventions, so every API
45
+ method name is in underscored, lowercase form:
46
+
47
+ SetServer -> set_server
48
+ RunQueries -> run_queries
49
+ SetMatchMode -> set_match_mode
50
+
51
+ Every method is aliased to a corresponding one from standard Sphinx
52
+ API, so you can use both `SetServer` and `set_server`
53
+ with no differrence.
54
+
55
+ There are three exceptions to this naming rule:
56
+
57
+ GetLastError -> last_error
58
+ GetLastWarning -> last_warning
59
+ IsConnectError -> connect_error?
60
+
61
+ Of course, all of them are aliased to the original method names.
62
+
63
+ ## Using multiple Sphinx servers
64
+
65
+ Since we actively use this plugin in our Scribd development workflow,
66
+ there are several methods have been added to accommodate our needs.
67
+ You can find documentation on Ruby-specific methods in [documentation](http://rdoc.info/projects/kpumuk/sphinx).
68
+
69
+ First of all, we added support of multiple Sphinx servers to balance
70
+ load between them. Also it means that in case of any problems with one
71
+ of servers, library will try to fetch the results from another one.
72
+ Every consequence request will be executed on the next server in list
73
+ (round-robin technique).
74
+
75
+ sphinx.set_servers([
76
+ { :host => 'browse01.local', :port => 3312 },
77
+ { :host => 'browse02.local', :port => 3312 },
78
+ { :host => 'browse03.local', :port => 3312 }
79
+ ])
80
+
81
+ By default library will try to fetch results from a single server, and
82
+ fail if it does not respond. To setup number of retries being performed,
83
+ you can use second (additional) parameter of the `set_connect_timeout`
84
+ and `set_request_timeout` methods:
85
+
86
+ sphinx.set_connect_timeout(1, 3)
87
+ sphinx.set_request_timeout(1, 3)
88
+
89
+ There is a big difference between these two methods. First will affect
90
+ only on requests experiencing problems with connection (socket error,
91
+ pipe error, etc), second will be used when request is broken somehow
92
+ (temporary searchd error, incomplete reply, etc). The workflow looks like
93
+ this:
94
+
95
+ 1. Increase retries number. If is less or equal to configured value,
96
+ try to connect to the next server. Otherwise, raise an error.
97
+ 2. In case of connection problem go to 1.
98
+ 3. Increase request retries number. If it less or equal to configured
99
+ value, try to perform request. Otherwise, raise an error.
100
+ 4. In case of connection problem go to 1.
101
+ 5. In case of request problem, go to 3.
102
+ 6. Parse and return response.
103
+
104
+ Withdrawals:
105
+
106
+ 1. Request could be performed `connect_retries` * `request_retries`
107
+ times. E.g., it could be tried `request_retries` times on each
108
+ of `connect_retries` servers (when you have 1 server configured,
109
+ but `connect_retries` is 5, library will try to connect to this
110
+ server 5 times).
111
+ 2. Request could be tried to execute on each server `1..request_retries`
112
+ times. In case of connection problem, request will be moved to another
113
+ server immediately.
114
+
115
+ Usually you will set `connect_retries` equal to servers number,
116
+ so you will be sure each failing request will be performed on all servers.
117
+ This means that if one of servers is live, but others are dead, you request
118
+ will be finally executed successfully.
119
+
120
+ ## Sphinx constants
121
+
122
+ Most Sphinx API methods expecting for special constants will be passed.
123
+ For example:
124
+
125
+ sphinx.set_match_mode(Sphinx::SPH_MATCH_ANY)
126
+
127
+ Please note that these constants defined in a `Sphinx`
128
+ module. You can use symbols or strings instead of these awful
129
+ constants:
130
+
131
+ sphinx.set_match_mode(:any)
132
+ sphinx.set_match_mode('any')
133
+
134
+ ## Setting query filters
135
+
136
+ Every `set_` method returns `Sphinx::Client` object itself.
137
+ It means that you can chain filtering methods:
138
+
139
+ results = Sphinx::Client.new.
140
+ set_match_mode(:any).
141
+ set_ranking_mode(:bm25).
142
+ set_id_range(10, 1000).
143
+ query('test')
144
+
145
+ There is a handful ability to set query parameters directly in `query`
146
+ call. If block does not accept any parameters, it will be eval'ed inside
147
+ Sphinx::Client instance:
148
+
149
+ results = Sphinx::Client.new.query('test') do
150
+ match_mode :any
151
+ ranking_mode :bm25
152
+ id_range 10, 1000
153
+ end
154
+
155
+ As you can see, in this case you can omit the `set_` prefix for
156
+ this methods. If block accepts a parameter, sphinx instance will be
157
+ passed into the block. In this case you should you full method names
158
+ including the `set_` prefix:
159
+
160
+ results = Sphinx::Client.new.query('test') do |sphinx|
161
+ sphinx.set_match_mode :any
162
+ sphinx.set_ranking_mode :bm25
163
+ sphinx.set_id_range 10, 1000
164
+ end
165
+
166
+ ## Example
167
+
168
+ This simple example illustrates base connection establishing,
169
+ search results retrieving, and excerpts building. Please note
170
+ how does it perform database select using ActiveRecord to
171
+ save the order of records established by Sphinx.
172
+
173
+ sphinx = Sphinx::Client.new
174
+ result = sphinx.query('test')
175
+ ids = result['matches'].map { |match| match['id'] }
176
+ posts = Post.all :conditions => { :id => ids },
177
+ :order => "FIELD(id,#{ids.join(',')})"
178
+
179
+ docs = posts.map(&:body)
180
+ excerpts = sphinx.build_excerpts(docs, 'index', 'test')
181
+
182
+ ## Logging
183
+
184
+ You can ask Sphinx client API to log it's activity to some log. In
185
+ order to do that you can pass a logger object into the `Sphinx::Client`
186
+ constructor:
187
+
188
+ require 'logger'
189
+ Sphinx::Client.new(Logger.new(STDOUT)).query('test')
190
+
191
+ Logger object should respond to methods :debug, :info, and :warn, and
192
+ accept blocks (this is what standard Ruby `Logger` class does).
193
+ Here is what you will see in your log:
194
+
195
+ * `DEBUG` -- `query`, `add_query`, `run_queries`
196
+ method calls with configured filters.
197
+ * `INFO` -- initialization with Sphinx version, servers change,
198
+ attempts to re-connect, and all attempts to do an API call with server
199
+ where request being performed.
200
+ * `WARN` -- various connection and socket errors.
201
+
202
+ ## Support
203
+
204
+ You can find source code for this library on [GitHub](http://github.com/kpumuk/sphinx).
205
+
206
+ To suggest a feature or report a bug use [GitHub Issues](http://github.com/kpumuk/sphinx/issues)
207
+
208
+ ## Credits
209
+
210
+ * [Dmytro Shteflyuk](https://github.com/kpumuk) (author)
211
+ * [Andrew Aksyonoff](http://sphinxsearch.com) (Sphinx core developer)
212
+
213
+ Special thanks to [Alexey Kovyrin](https://github.com/kovyrin)
214
+
215
+ Special thanks to [Mike Perham](https://github.com/mperham) for his awesome
216
+ memcache-client gem, where latest Sphinx gem got new sockets handling from.
217
+
218
+ ## License
219
+
220
+ This library is distributed under the terms of the Ruby license.
221
+ You can freely distribute/modify this library.
data/Rakefile CHANGED
@@ -1,45 +1,50 @@
1
- require 'rake'
2
-
3
- begin
4
- require 'jeweler'
5
- Jeweler::Tasks.new do |gemspec|
6
- gemspec.name = 'sphinx'
7
- gemspec.summary = 'Sphinx Client API for Ruby'
8
- gemspec.description = '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.'
9
- gemspec.email = 'kpumuk@kpumuk.info'
10
- gemspec.homepage = 'http://github.com/kpumuk/sphinx'
11
- gemspec.authors = ['Dmytro Shteflyuk']
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :test => :spec
8
+ task :default => :spec
9
+
10
+ require 'yard'
11
+ YARD::Rake::YardocTask.new(:yard) do |t|
12
+ t.options = ['--title', 'Sphinx Client API Documentation']
13
+ if ENV['PRIVATE']
14
+ t.options.concat ['--protected', '--private']
15
+ else
16
+ t.options.concat ['--protected', '--no-private']
12
17
  end
13
- Jeweler::GemcutterTasks.new
14
- rescue LoadError
15
- puts 'Jeweler not available. Install it with: sudo gem install jeweler'
16
18
  end
17
19
 
18
- begin
19
- require 'spec/rake/spectask'
20
+ require 'bundler'
21
+ Bundler::GemHelper.install_tasks
20
22
 
21
- desc 'Default: run specs'
22
- task :default => :spec
23
+ namespace :fixtures do
24
+ FIXTURES_DIR = File.expand_path('../spec/fixtures', __FILE__)
23
25
 
24
- desc 'Test the sphinx plugin'
25
- Spec::Rake::SpecTask.new do |t|
26
- t.libs << 'lib'
27
- t.pattern = 'spec/*_spec.rb'
26
+ desc 'Update textures for sphinx requests'
27
+ task :requests do
28
+ rm Dir.glob("#{FIXTURES_DIR}/requests/*.dat")
29
+ Dir["#{FIXTURES_DIR}/requests/php/*.php"].each do |file|
30
+ puts name = File.basename(file, '.php')
31
+ File.open(File.join(File.dirname(file), '..', "#{name}.dat"), 'w') do |f|
32
+ f.write `env SPHINX_MOCK_REQUEST=1 php "#{file}"`
33
+ end
34
+ end
28
35
  end
29
- rescue LoadError
30
- puts 'RSpec not available. Install it with: sudo gem install rspec'
31
- end
32
36
 
33
- begin
34
- require 'yard'
35
- YARD::Rake::YardocTask.new(:yard) do |t|
36
- t.options = ['--title', 'Sphinx Client API Documentation']
37
- if ENV['PRIVATE']
38
- t.options.concat ['--protected', '--private']
39
- else
40
- t.options << '--no-private'
37
+ desc 'Update textures for sphinx responses'
38
+ task :responses do
39
+ rm Dir.glob("#{FIXTURES_DIR}/responses/*.dat")
40
+ Dir["#{FIXTURES_DIR}/responses/php/*.php"].each do |file|
41
+ puts name = File.basename(file, '.php')
42
+ File.open(File.join(File.dirname(file), '..', "#{name}.dat"), 'w') do |f|
43
+ f.write `env SPHINX_MOCK_RESPONSE=1 php "#{file}"`
44
+ end
41
45
  end
42
46
  end
43
- rescue LoadError
44
- puts 'Yard not available. Install it with: sudo gem install yard'
45
47
  end
48
+
49
+ desc 'Update binary fixtures'
50
+ task :fixtures => %w[ fixtures:requests fixtures:responses]
@@ -1,9 +1,9 @@
1
1
  # Sphinx Client API
2
2
  #
3
3
  # Author:: Dmytro Shteflyuk <mailto:kpumuk@kpumuk.info>.
4
- # Copyright:: Copyright (c) 2006 — 2009 Dmytro Shteflyuk
4
+ # Copyright:: Copyright (c) 2006 — 2013 Dmytro Shteflyuk
5
5
  # License:: Distributes under the same terms as Ruby
6
- # Version:: 0.9.10-r2122
6
+ # Version:: 0.9.10.2122
7
7
  # Website:: http://kpumuk.info/projects/ror-plugins/sphinx
8
8
  # Sources:: http://github.com/kpumuk/sphinx
9
9
  #
@@ -11,11 +11,6 @@
11
11
  # You can freely distribute/modify this library.
12
12
  #
13
13
  module Sphinx
14
- VERSION = begin
15
- config = YAML.load(File.read(File.dirname(__FILE__) + '/../VERSION.yml'))
16
- "#{config[:major]}.#{config[:minor]}.#{config[:patch]}.#{config[:build]}"
17
- end
18
-
19
14
  # Base class for all Sphinx errors
20
15
  class SphinxError < StandardError; end
21
16
 
@@ -40,8 +35,8 @@ require 'socket'
40
35
  require 'zlib'
41
36
 
42
37
  path = File.dirname(__FILE__)
43
- require "#{path}/sphinx/constants"
44
38
  require "#{path}/sphinx/indifferent_access"
39
+ require "#{path}/sphinx/constants"
45
40
  require "#{path}/sphinx/request"
46
41
  require "#{path}/sphinx/response"
47
42
  require "#{path}/sphinx/timeout"
@@ -14,7 +14,7 @@ module Sphinx
14
14
  #
15
15
  class Client
16
16
  include Sphinx::Constants
17
-
17
+
18
18
  #=================================================================
19
19
  # Some internal attributes to use inside client API
20
20
  #=================================================================
@@ -66,10 +66,17 @@ module Sphinx
66
66
  @anchor = [] # geographical anchor point
67
67
  @indexweights = [] # per-index weights
68
68
  @ranker = SPH_RANK_PROXIMITY_BM25 # ranking mode (default is SPH_RANK_PROXIMITY_BM25)
69
+ @rankexpr = '' # ranking expression
69
70
  @maxquerytime = 0 # max query time, milliseconds (default is 0, do not limit)
70
71
  @fieldweights = {} # per-field-name weights
71
72
  @overrides = [] # per-query attribute values overrides
72
73
  @select = '*' # select-list (attributes or expressions, with optional aliases)
74
+ @query_flags = 0
75
+ @predictedtime = 0
76
+ @outerorderby = ''
77
+ @outeroffset = 0
78
+ @outerlimit = 0
79
+ @hasouter = false
73
80
 
74
81
  # per-reply fields (for single-query case)
75
82
  @error = '' # last error message
@@ -107,8 +114,8 @@ module Sphinx
107
114
  :overrides => @overrides,
108
115
  :select => @select,
109
116
  :match_mode => @mode,
110
- :ranking_mode => @ranker,
111
- :sort_mode => { :mode => @sort, :sortby => @sortby },
117
+ :ranking => { :mode => @ranker, :expression => @rankexpr },
118
+ :sort_mode => { :mode => @sort, :sort_by => @sortby },
112
119
  :weights => @weights,
113
120
  :field_weights => @fieldweights,
114
121
  :index_weights => @indexweights,
@@ -116,11 +123,12 @@ module Sphinx
116
123
  :filters => @filters,
117
124
  :geo_anchor => @anchor,
118
125
  :group_by => { :attribute => @groupby, :func => @groupfunc, :sort => @groupsort },
119
- :group_distinct => @groupdistinct
126
+ :group_distinct => @groupdistinct,
127
+ :query_flags => { :bitset => @query_flags, :predicted_time => @predictedtime },
128
+ :outer_select => { :has_outer => @hasouter, :sort_by => @outerorderby, :offset => @outeroffset, :limit => @outerlimit},
120
129
  }
121
130
 
122
- "<Sphinx::Client: %d servers, params: %s>" %
123
- [@servers.length, params.inspect]
131
+ "<Sphinx::Client: %d servers, params: %s>" % [@servers.length, params.inspect]
124
132
  end
125
133
 
126
134
  #=================================================================
@@ -609,6 +617,61 @@ module Sphinx
609
617
  end
610
618
  alias :SetSelect :set_select
611
619
 
620
+ # Allows to control a number of per-query options.
621
+ #
622
+ # Supported options and respectively allowed values are:
623
+ #
624
+ # * +reverse_scan+ -- +0+ or +1+, lets you control the order in which full-scan query processes the rows.
625
+ # * +sort_method+ -- +"pq"+ (priority queue, set by default) or +"kbuffer"+
626
+ # (gives faster sorting for already pre-sorted data, e.g. index data sorted by id).
627
+ # The result set is in both cases the same; picking one option or the other
628
+ # may just improve (or worsen!) performance.
629
+ # * +boolean_simplify+ -- +false+ or +true+, enables simplifying the query to speed it up.
630
+ # * +idf+ -- either +"normalized"+ (default) or +"plain"+.
631
+ #
632
+ # @param [String] flag_name name of the option to set value for
633
+ # @param [Object] flag_value value to set
634
+ # @return [Sphinx::Client] self.
635
+ #
636
+ # @see http://sphinxsearch.com/docs/current.html#sphinxql-select
637
+ # @see http://sphinxsearch.com/docs/current.html#conf-predicted-time-costs
638
+ #
639
+ def set_query_flag(flag_name, flag_value)
640
+ raise ArgumentError, 'unknown "flag_name" argument value' unless QUERY_FLAGS.has_key?(flag_name)
641
+
642
+ flag = QUERY_FLAGS[flag_name]
643
+ values = QUERY_FLAGS[flag_name][:values]
644
+
645
+ if flag_name.to_s == 'max_predicted_time'
646
+ raise ArgumentError, "\"flag_value\" should be a positive integer for \"max_predicted_time\" flag" unless flag_value.kind_of?(Integer) and flag_value >= 0
647
+
648
+ @predictedtime = flag_value
649
+ elsif !values.include?(flag_value)
650
+ raise ArgumentError, "unknown \"flag_value\", should be one of #{values.inspect}"
651
+ end
652
+
653
+ is_set = values.respond_to?(:call) ? values.call(flag_value) : values.index(flag_value) == 1
654
+ @query_flags = set_bit(@query_flags, flag[:index], is_set)
655
+ self
656
+ end
657
+ alias :SetQueryFlag set_query_flag
658
+
659
+ def set_outer_select(orderby, offset, limit)
660
+ raise ArgumentError, '"orderby" argument must be String' unless orderby.kind_of?(String)
661
+ raise ArgumentError, '"offset" argument must be Integer' unless offset.kind_of?(Integer)
662
+ raise ArgumentError, '"limit" argument must be Integer' unless limit.kind_of?(Integer)
663
+
664
+ raise ArgumentError, '"offset" argument should be greater or equal to zero' unless offset >= 0
665
+ raise ArgumentError, '"limit" argument should be greater to zero' unless limit > 0
666
+
667
+ @outerorderby = orderby
668
+ @outeroffset = offset
669
+ @outerlimit = limit
670
+ @hasouter = true
671
+ self
672
+ end
673
+ alias :SetOuterSelect set_outer_select
674
+
612
675
  #=================================================================
613
676
  # Full-text search query settings
614
677
  #=================================================================
@@ -655,17 +718,33 @@ module Sphinx
655
718
  # matching mode at the time of this writing. Parameter must be a
656
719
  # constant specifying one of the known modes.
657
720
  #
721
+ # By default, in the +EXTENDED+ matching mode Sphinx computes two
722
+ # factors which contribute to the final match weight. The major
723
+ # part is a phrase proximity value between the document text and
724
+ # the query. The minor part is so-called BM25 statistical function,
725
+ # which varies from 0 to 1 depending on the keyword frequency within
726
+ # document (more occurrences yield higher weight) and within the whole
727
+ # index (more rare keywords yield higher weight).
728
+ #
729
+ # However, in some cases you'd want to compute weight differently - or
730
+ # maybe avoid computing it at all for performance reasons because you're
731
+ # sorting the result set by something else anyway. This can be accomplished
732
+ # by setting the appropriate ranking mode.
733
+ #
658
734
  # You can specify ranking mode as String ("proximity_bm25", "bm25", etc),
659
735
  # Symbol (:proximity_bm25, :bm25, etc), or
660
736
  # Fixnum constant (SPH_RANK_PROXIMITY_BM25, SPH_RANK_BM25, etc).
661
737
  #
662
738
  # @param [Integer, String, Symbol] ranker ranking mode.
739
+ # @param [String] rankexpr ranking formula to use with the expression
740
+ # based ranker (+SPH_RANK_EXPR+).
663
741
  # @return [Sphinx::Client] self.
664
742
  #
665
743
  # @example
666
744
  # sphinx.set_ranking_mode(Sphinx::SPH_RANK_BM25)
667
745
  # sphinx.set_ranking_mode(:bm25)
668
746
  # sphinx.set_ranking_mode('bm25')
747
+ # sphinx.set_ranking_mode(:expr, 'sum(lcs*user_weight)*1000+bm25')
669
748
  #
670
749
  # @raise [ArgumentError] Occurred when parameters are invalid.
671
750
  #
@@ -673,21 +752,26 @@ module Sphinx
673
752
  # @see http://www.sphinxsearch.com/docs/current.html#api-func-setmatchmode Section 6.3.1, "SetMatchMode"
674
753
  # @see http://www.sphinxsearch.com/docs/current.html#api-func-setrankingmode Section 6.3.2, "SetRankingMode"
675
754
  #
676
- def set_ranking_mode(ranker)
755
+ def set_ranking_mode(ranker, rankexpr = '')
677
756
  case ranker
678
757
  when String, Symbol
679
- begin
680
- ranker = self.class.const_get("SPH_RANK_#{ranker.to_s.upcase}")
681
- rescue NameError
758
+ const_name = "SPH_RANK_#{ranker.to_s.upcase}"
759
+ unless self.class.const_defined?(const_name)
682
760
  raise ArgumentError, "\"ranker\" argument value \"#{ranker}\" is invalid"
683
761
  end
762
+
763
+ ranker = self.class.const_get(const_name)
684
764
  when Fixnum
685
765
  raise ArgumentError, "\"ranker\" argument value \"#{ranker}\" is invalid" unless (SPH_RANK_PROXIMITY_BM25..SPH_RANK_SPH04).include?(ranker)
686
766
  else
687
767
  raise ArgumentError, '"ranker" argument must be Fixnum, String, or Symbol'
688
768
  end
689
769
 
770
+ raise ArgumentError, '"rankexpr" argument must be String' unless rankexpr.kind_of?(String)
771
+ raise ArgumentError, '"rankexpr" should not be empty if ranker is SPH_RANK_EXPR' if ranker == SPH_RANK_EXPR and rankexpr.empty?
772
+
690
773
  @ranker = ranker
774
+ @rankexpr = rankexpr
691
775
  self
692
776
  end
693
777
  alias :SetRankingMode :set_ranking_mode
@@ -1259,6 +1343,22 @@ module Sphinx
1259
1343
  end
1260
1344
  alias :ResetOverrides :reset_overrides
1261
1345
 
1346
+ def reset_query_flag
1347
+ @query_flags = 0
1348
+ @predictedtime = 0
1349
+ self
1350
+ end
1351
+ alias :ResetQueryFlag :reset_query_flag
1352
+
1353
+ def reset_outer_select
1354
+ @outerorderby = ''
1355
+ @outeroffset = 0
1356
+ @outerlimit = 0
1357
+ @hasouter = 0
1358
+ self
1359
+ end
1360
+ alias :ResetOuterSelect :reset_outer_select
1361
+
1262
1362
  # Connects to searchd server, runs given search query with
1263
1363
  # current settings, obtains and returns the result set.
1264
1364
  #
@@ -1473,7 +1573,12 @@ module Sphinx
1473
1573
 
1474
1574
  # mode and limits
1475
1575
  request = Request.new
1476
- request.put_int @offset, @limit, @mode, @ranker, @sort
1576
+ request.put_int @query_flags, @offset, @limit, @mode
1577
+ # ranker
1578
+ request.put_int @ranker
1579
+ request.put_string @rankexpr if @ranker == SPH_RANK_EXPR
1580
+ # sorting
1581
+ request.put_int @sort
1477
1582
  request.put_string @sortby
1478
1583
  # query itself
1479
1584
  request.put_string query
@@ -1524,7 +1629,7 @@ module Sphinx
1524
1629
 
1525
1630
  # per-index weights
1526
1631
  request.put_int @indexweights.length
1527
- @indexweights.each do |idx, weight|
1632
+ @indexweights.sort_by { |idx, _| idx }.each do |idx, weight|
1528
1633
  request.put_string idx.to_s
1529
1634
  request.put_int weight
1530
1635
  end
@@ -1534,7 +1639,7 @@ module Sphinx
1534
1639
 
1535
1640
  # per-field weights
1536
1641
  request.put_int @fieldweights.length
1537
- @fieldweights.each do |field, weight|
1642
+ @fieldweights.sort_by { |idx, _| idx }.each do |field, weight|
1538
1643
  request.put_string field.to_s
1539
1644
  request.put_int weight
1540
1645
  end
@@ -1563,6 +1668,13 @@ module Sphinx
1563
1668
  # select-list
1564
1669
  request.put_string @select
1565
1670
 
1671
+ # max_predicted_time
1672
+ request.put_int @predictedtime if @predictedtime > 0
1673
+
1674
+ # outer select
1675
+ request.put_string @outerorderby
1676
+ request.put_int @outeroffset, @outerlimit, (@hasouter ? 1 : 0)
1677
+
1566
1678
  # store request to requests array
1567
1679
  @reqs << request.to_s;
1568
1680
  return @reqs.length - 1
@@ -1611,52 +1723,49 @@ module Sphinx
1611
1723
 
1612
1724
  reqs, nreqs = @reqs.join(''), @reqs.length
1613
1725
  @reqs = []
1614
- response = perform_request(:search, reqs, nreqs)
1726
+ response = perform_request(:search, reqs, [0, nreqs])
1615
1727
 
1616
1728
  # parse response
1617
1729
  (1..nreqs).map do
1618
- result = HashWithIndifferentAccess.new('error' => '', 'warning' => '')
1730
+ result = HashWithIndifferentAccess.new(:error => '', :warning => '')
1619
1731
 
1620
1732
  # extract status
1621
- status = result['status'] = response.get_int
1733
+ status = result[:status] = response.get_int
1622
1734
  if status != SEARCHD_OK
1623
1735
  message = response.get_string
1624
1736
  if status == SEARCHD_WARNING
1625
- result['warning'] = message
1737
+ result[:warning] = message
1626
1738
  else
1627
- result['error'] = message
1739
+ result[:error] = message
1628
1740
  next result
1629
1741
  end
1630
1742
  end
1631
1743
 
1632
1744
  # read schema
1633
1745
  nfields = response.get_int
1634
- result['fields'] = (1..nfields).map { response.get_string }
1746
+ result[:fields] = (1..nfields).map { response.get_string }
1635
1747
 
1636
1748
  attrs_names_in_order = []
1637
1749
  nattrs = response.get_int
1638
- attrs = (1..nattrs).inject({}) do |hash, idx|
1750
+ attrs = nattrs.times.inject(HashWithIndifferentAccess.new) do |hash, idx|
1639
1751
  name, type = response.get_string, response.get_int
1640
1752
  hash[name] = type
1641
1753
  attrs_names_in_order << name
1642
1754
  hash
1643
1755
  end
1644
- result['attrs'] = attrs
1756
+ result[:attrs] = attrs
1645
1757
 
1646
1758
  # read match count
1647
1759
  count, id64 = response.get_ints(2)
1648
1760
 
1649
1761
  # read matches
1650
- result['matches'] = (1..count).map do
1651
- doc, weight = if id64 == 0
1652
- response.get_ints(2)
1653
- else
1654
- [response.get_int64, response.get_int]
1655
- end
1762
+ result[:matches] = (1..count).map do
1763
+ doc = id64 == 0 ? response.get_int : response.get_int64
1764
+ weight = response.get_int
1656
1765
 
1657
1766
  # This is a single result put in the result['matches'] array
1658
- match = { 'id' => doc, 'weight' => weight }
1659
- match['attrs'] = attrs_names_in_order.inject({}) do |hash, name|
1767
+ match = HashWithIndifferentAccess.new(:id => doc, :weight => weight)
1768
+ match[:attrs] = attrs_names_in_order.inject(HashWithIndifferentAccess.new) do |hash, name|
1660
1769
  hash[name] = case attrs[name]
1661
1770
  when SPH_ATTR_BIGINT
1662
1771
  # handle 64-bit ints
@@ -1665,28 +1774,35 @@ module Sphinx
1665
1774
  # handle floats
1666
1775
  response.get_float
1667
1776
  when SPH_ATTR_STRING
1777
+ # handle string
1668
1778
  response.get_string
1779
+ when SPH_ATTR_FACTORS
1780
+ # ???
1781
+ response.get_int
1782
+ when SPH_ATTR_MULTI
1783
+ # handle array of integers
1784
+ val = response.get_int
1785
+ response.get_ints(val) if val > 0
1786
+ when SPH_ATTR_MULTI64
1787
+ # handle array of 64-bit integers
1788
+ val = response.get_int
1789
+ (val / 2).times.map { response.get_int64 }
1669
1790
  else
1670
1791
  # handle everything else as unsigned ints
1671
- val = response.get_int
1672
- if (attrs[name] & SPH_ATTR_MULTI) != 0
1673
- (1..val).map { response.get_int }
1674
- else
1675
- val
1676
- end
1792
+ response.get_int
1677
1793
  end
1678
1794
  hash
1679
1795
  end
1680
1796
  match
1681
1797
  end
1682
- result['total'], result['total_found'], msecs = response.get_ints(3)
1683
- result['time'] = '%.3f' % (msecs / 1000.0)
1798
+ result[:total], result[:total_found], msecs = response.get_ints(3)
1799
+ result[:time] = '%.3f' % (msecs / 1000.0)
1684
1800
 
1685
1801
  nwords = response.get_int
1686
- result['words'] = (1..nwords).inject({}) do |hash, idx|
1802
+ result[:words] = nwords.times.inject({}) do |hash, idx|
1687
1803
  word = response.get_string
1688
1804
  docs, hits = response.get_ints(2)
1689
- hash[word] = { 'docs' => docs, 'hits' => hits }
1805
+ hash[word] = HashWithIndifferentAccess.new(:docs => docs, :hits => hits)
1690
1806
  hash
1691
1807
  end
1692
1808
 
@@ -1761,27 +1877,42 @@ module Sphinx
1761
1877
 
1762
1878
  # fixup options
1763
1879
  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
1880
+ :before_match => '<b>',
1881
+ :after_match => '</b>',
1882
+ :chunk_separator => ' ... ',
1883
+ :limit => 256,
1884
+ :limit_passages => 0,
1885
+ :limit_words => 0,
1886
+ :around => 5,
1887
+ :exact_phrase => false,
1888
+ :single_passage => false,
1889
+ :use_boundaries => false,
1890
+ :weight_order => false,
1891
+ :query_mode => false,
1892
+ :force_all_words => false,
1893
+ :start_passage_id => 1,
1894
+ :load_files => false,
1895
+ :html_strip_mode => 'index',
1896
+ :allow_empty => false,
1897
+ :passage_boundary => 'none',
1898
+ :emit_zones => false,
1899
+ :load_files_scattered => false
1774
1900
  ).update(opts)
1775
1901
 
1776
1902
  # build request
1777
1903
 
1778
- # v.1.0 req
1779
- flags = 1
1780
- flags |= 2 if opts['exact_phrase']
1781
- flags |= 4 if opts['single_passage']
1782
- flags |= 8 if opts['use_boundaries']
1783
- flags |= 16 if opts['weight_order']
1784
- flags |= 32 if opts['query_mode']
1904
+ # v.1.2 req
1905
+ flags = 1
1906
+ flags |= 2 if opts[:exact_phrase]
1907
+ flags |= 4 if opts[:single_passage]
1908
+ flags |= 8 if opts[:use_boundaries]
1909
+ flags |= 16 if opts[:weight_order]
1910
+ flags |= 32 if opts[:query_mode]
1911
+ flags |= 64 if opts[:force_all_words]
1912
+ flags |= 128 if opts[:load_files]
1913
+ flags |= 256 if opts[:allow_empty]
1914
+ flags |= 512 if opts[:emit_zones]
1915
+ flags |= 1024 if opts[:load_files_scattered]
1785
1916
 
1786
1917
  request = Request.new
1787
1918
  request.put_int 0, flags # mode=0, flags=1 (remove spaces)
@@ -1791,10 +1922,10 @@ module Sphinx
1791
1922
  request.put_string words
1792
1923
 
1793
1924
  # options
1794
- request.put_string opts['before_match']
1795
- request.put_string opts['after_match']
1796
- request.put_string opts['chunk_separator']
1797
- request.put_int opts['limit'].to_i, opts['around'].to_i
1925
+ request.put_string opts[:before_match], opts[:after_match], opts[:chunk_separator]
1926
+ request.put_int opts[:limit].to_i, opts[:around].to_i
1927
+ request.put_int opts[:limit_passages].to_i, opts[:limit_words].to_i, opts[:start_passage_id].to_i
1928
+ request.put_string opts[:html_strip_mode], opts[:passage_boundary]
1798
1929
 
1799
1930
  # documents
1800
1931
  request.put_int docs.size
@@ -1918,10 +2049,11 @@ module Sphinx
1918
2049
  #
1919
2050
  # @see http://www.sphinxsearch.com/docs/current.html#api-func-updateatttributes Section 6.7.2, "UpdateAttributes"
1920
2051
  #
1921
- def update_attributes(index, attrs, values, mva = false)
2052
+ def update_attributes(index, attrs, values, mva = false, ignore_non_existent = false)
1922
2053
  # verify everything
1923
2054
  raise ArgumentError, '"index" argument must be String' unless index.kind_of?(String) or index.kind_of?(Symbol)
1924
2055
  raise ArgumentError, '"mva" argument must be Boolean' unless mva.kind_of?(TrueClass) or mva.kind_of?(FalseClass)
2056
+ raise ArgumentError, '"ignore_non_existent" argument must be Boolean' unless ignore_non_existent.kind_of?(TrueClass) or ignore_non_existent.kind_of?(FalseClass)
1925
2057
 
1926
2058
  raise ArgumentError, '"attrs" argument must be Array' unless attrs.kind_of?(Array)
1927
2059
  attrs.each do |attr|
@@ -1950,6 +2082,7 @@ module Sphinx
1950
2082
  request.put_string index
1951
2083
 
1952
2084
  request.put_int attrs.length
2085
+ request.put_int ignore_non_existent ? 1 : 0
1953
2086
  for attr in attrs
1954
2087
  request.put_string attr
1955
2088
  request.put_int mva ? 1 : 0
@@ -2051,7 +2184,7 @@ module Sphinx
2051
2184
  # @example
2052
2185
  # sphinx.flush_attrs
2053
2186
  #
2054
- def flush_attrs
2187
+ def flush_attributes
2055
2188
  request = Request.new
2056
2189
  response = perform_request(:flushattrs, request)
2057
2190
 
@@ -2059,10 +2192,13 @@ module Sphinx
2059
2192
  begin
2060
2193
  response.get_int
2061
2194
  rescue EOFError
2195
+ @error = 'unexpected response length'
2062
2196
  -1
2063
2197
  end
2064
2198
  end
2065
- alias :FlushAttrs :flush_attrs
2199
+ alias :FlushAttributes :flush_attributes
2200
+ alias :FlushAttrs :flush_attributes
2201
+ alias :flush_attrs :flush_attributes
2066
2202
 
2067
2203
  #=================================================================
2068
2204
  # Persistent connections
@@ -2152,7 +2288,7 @@ module Sphinx
2152
2288
  # <tt>:update</tt>, <tt>:keywords</tt>, <tt>:persist</tt>, <tt>:status</tt>,
2153
2289
  # <tt>:query</tt>, <tt>:flushattrs</tt>. See <tt>SEARCHD_COMMAND_*</tt> for details).
2154
2290
  # @param [Sphinx::Request] request contains request body.
2155
- # @param [Integer] additional additional integer data to be placed between header and body.
2291
+ # @param [Integer, Array] additional additional integer or array of integers data to be placed between header and body.
2156
2292
  # @param [Sphinx::Server] server where perform request on. This is special
2157
2293
  # parameter for internal usage. If specified, request will be performed
2158
2294
  # on specified server, and it will try to establish connection to this
@@ -2182,22 +2318,23 @@ module Sphinx
2182
2318
  attempts = nil
2183
2319
  end
2184
2320
 
2185
- with_server(server, attempts) do |server|
2186
- logger.info { "[sphinx] #{command} on server #{server}" } if logger
2321
+ with_server(server, attempts) do |srv|
2322
+ logger.info { "[sphinx] #{command} on server #{srv}" } if logger
2187
2323
 
2188
2324
  cmd = command.to_s.upcase
2189
2325
  command_id = Sphinx::Client.const_get("SEARCHD_COMMAND_#{cmd}")
2190
2326
  command_ver = Sphinx::Client.const_get("VER_COMMAND_#{cmd}")
2191
2327
 
2192
- with_socket(server) do |socket|
2193
- len = request.to_s.length + (additional.nil? ? 0 : 4)
2328
+ with_socket(srv) do |socket|
2329
+ additional = Array(additional)
2330
+ len = request.to_s.length + (additional.size * 4)
2194
2331
  header = [command_id, command_ver, len].pack('nnN')
2195
- header << [additional].pack('N') unless additional.nil?
2332
+ header << additional.pack('N' * additional.size) unless additional.empty?
2196
2333
 
2197
2334
  socket.write(header + request.to_s)
2198
2335
 
2199
2336
  if block_given?
2200
- yield server, socket
2337
+ yield srv, socket
2201
2338
  else
2202
2339
  parse_response(socket, command_ver)
2203
2340
  end
@@ -2431,6 +2568,22 @@ module Sphinx
2431
2568
  end
2432
2569
  end
2433
2570
 
2571
+ # Sets or resets given bit in a bitset.
2572
+ #
2573
+ # @param [Integer] bitset integer value to set bit in.
2574
+ # @param [Integer] index integer offset of the bit to set.
2575
+ # @param [Boolean,Integer] value value to set bit into (+true+, +false+, +0+, or +1+).
2576
+ #
2577
+ def set_bit(bitset, index, value)
2578
+ bit = 1 << index
2579
+ if value == true || value == 1
2580
+ bitset |= bit
2581
+ elsif bitset & bit > 0
2582
+ bitset ^= bit
2583
+ end
2584
+ bitset
2585
+ end
2586
+
2434
2587
  # Enables ability to skip +set_+ prefix for methods inside {#query} block.
2435
2588
  #
2436
2589
  # @example