sphinx 0.9.10.2122 → 2.1.1.3711

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