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.
- data/.gitignore +5 -2
- data/.travis.yml +8 -0
- data/CHANGELOG.md +71 -0
- data/Gemfile +4 -0
- data/README.md +221 -0
- data/Rakefile +40 -35
- data/lib/sphinx.rb +3 -8
- data/lib/sphinx/client.rb +223 -70
- data/lib/sphinx/constants.rb +23 -8
- data/lib/sphinx/response.rb +6 -6
- data/lib/sphinx/timeout.rb +1 -2
- data/lib/sphinx/version.rb +3 -0
- data/spec/client_response_spec.rb +77 -64
- data/spec/client_spec.rb +68 -31
- data/spec/client_validations_spec.rb +61 -7
- data/spec/fixtures/requests/default_search.dat +0 -0
- data/spec/fixtures/requests/default_search_index.dat +0 -0
- data/spec/fixtures/requests/excerpt_custom.dat +0 -0
- data/spec/fixtures/requests/excerpt_default.dat +0 -0
- data/spec/fixtures/requests/excerpt_flags.dat +0 -0
- data/spec/fixtures/requests/field_weights.dat +0 -0
- data/spec/fixtures/requests/filter.dat +0 -0
- data/spec/fixtures/requests/filter_exclude.dat +0 -0
- data/spec/fixtures/requests/filter_float_range.dat +0 -0
- data/spec/fixtures/requests/filter_float_range_exclude.dat +0 -0
- data/spec/fixtures/requests/filter_range.dat +0 -0
- data/spec/fixtures/requests/filter_range_exclude.dat +0 -0
- data/spec/fixtures/requests/filter_range_int64.dat +0 -0
- data/spec/fixtures/requests/filter_ranges.dat +0 -0
- data/spec/fixtures/requests/filters.dat +0 -0
- data/spec/fixtures/requests/filters_different.dat +0 -0
- data/spec/fixtures/requests/geo_anchor.dat +0 -0
- data/spec/fixtures/requests/group_by_attr.dat +0 -0
- data/spec/fixtures/requests/group_by_attrpair.dat +0 -0
- data/spec/fixtures/requests/group_by_day.dat +0 -0
- data/spec/fixtures/requests/group_by_day_sort.dat +0 -0
- data/spec/fixtures/requests/group_by_month.dat +0 -0
- data/spec/fixtures/requests/group_by_week.dat +0 -0
- data/spec/fixtures/requests/group_by_year.dat +0 -0
- data/spec/fixtures/requests/group_distinct.dat +0 -0
- data/spec/fixtures/requests/id_range.dat +0 -0
- data/spec/fixtures/requests/id_range64.dat +0 -0
- data/spec/fixtures/requests/index_weights.dat +0 -0
- data/spec/fixtures/requests/keywords.dat +0 -0
- data/spec/fixtures/requests/limits.dat +0 -0
- data/spec/fixtures/requests/limits_cutoff.dat +0 -0
- data/spec/fixtures/requests/limits_max.dat +0 -0
- data/spec/fixtures/requests/limits_max_cutoff.dat +0 -0
- data/spec/fixtures/requests/match_all.dat +0 -0
- data/spec/fixtures/requests/match_any.dat +0 -0
- data/spec/fixtures/requests/match_boolean.dat +0 -0
- data/spec/fixtures/requests/match_extended.dat +0 -0
- data/spec/fixtures/requests/match_extended2.dat +0 -0
- data/spec/fixtures/requests/match_fullscan.dat +0 -0
- data/spec/fixtures/requests/match_phrase.dat +0 -0
- data/spec/fixtures/requests/max_query_time.dat +0 -0
- data/spec/fixtures/requests/miltiple_queries.dat +0 -0
- data/spec/fixtures/requests/outer_select.dat +0 -0
- data/spec/fixtures/requests/override.dat +0 -0
- data/spec/fixtures/{default_search.php → requests/php/default_search.php} +1 -1
- data/spec/fixtures/{default_search_index.php → requests/php/default_search_index.php} +1 -1
- data/spec/fixtures/{excerpt_custom.php → requests/php/excerpt_custom.php} +1 -1
- data/spec/fixtures/{excerpt_default.php → requests/php/excerpt_default.php} +1 -1
- data/spec/fixtures/{excerpt_flags.php → requests/php/excerpt_flags.php} +1 -1
- data/spec/fixtures/{field_weights.php → requests/php/field_weights.php} +1 -1
- data/spec/fixtures/{filter.php → requests/php/filter.php} +1 -1
- data/spec/fixtures/{filter_exclude.php → requests/php/filter_exclude.php} +1 -1
- data/spec/fixtures/{filter_float_range.php → requests/php/filter_float_range.php} +1 -1
- data/spec/fixtures/{filter_float_range_exclude.php → requests/php/filter_float_range_exclude.php} +1 -1
- data/spec/fixtures/{filter_range.php → requests/php/filter_range.php} +1 -1
- data/spec/fixtures/{filter_range_exclude.php → requests/php/filter_range_exclude.php} +1 -1
- data/spec/fixtures/{filter_range_int64.php → requests/php/filter_range_int64.php} +1 -1
- data/spec/fixtures/{filter_ranges.php → requests/php/filter_ranges.php} +1 -1
- data/spec/fixtures/{filters.php → requests/php/filters.php} +1 -1
- data/spec/fixtures/{filters_different.php → requests/php/filters_different.php} +1 -1
- data/spec/fixtures/{geo_anchor.php → requests/php/geo_anchor.php} +1 -1
- data/spec/fixtures/{group_by_attr.php → requests/php/group_by_attr.php} +1 -1
- data/spec/fixtures/{group_by_attrpair.php → requests/php/group_by_attrpair.php} +1 -1
- data/spec/fixtures/{group_by_day.php → requests/php/group_by_day.php} +1 -1
- data/spec/fixtures/{group_by_day_sort.php → requests/php/group_by_day_sort.php} +1 -1
- data/spec/fixtures/{group_by_month.php → requests/php/group_by_month.php} +1 -1
- data/spec/fixtures/{group_by_week.php → requests/php/group_by_week.php} +1 -1
- data/spec/fixtures/{group_by_year.php → requests/php/group_by_year.php} +1 -1
- data/spec/fixtures/{group_distinct.php → requests/php/group_distinct.php} +1 -1
- data/spec/fixtures/{id_range.php → requests/php/id_range.php} +1 -1
- data/spec/fixtures/{id_range64.php → requests/php/id_range64.php} +1 -1
- data/spec/fixtures/{index_weights.php → requests/php/index_weights.php} +1 -1
- data/spec/fixtures/{keywords.php → requests/php/keywords.php} +1 -1
- data/spec/fixtures/{limits.php → requests/php/limits.php} +1 -1
- data/spec/fixtures/{limits_cutoff.php → requests/php/limits_cutoff.php} +1 -1
- data/spec/fixtures/{limits_max.php → requests/php/limits_max.php} +1 -1
- data/spec/fixtures/{limits_max_cutoff.php → requests/php/limits_max_cutoff.php} +1 -1
- data/spec/fixtures/{match_all.php → requests/php/match_all.php} +1 -1
- data/spec/fixtures/{match_any.php → requests/php/match_any.php} +1 -1
- data/spec/fixtures/{match_boolean.php → requests/php/match_boolean.php} +1 -1
- data/spec/fixtures/{match_extended.php → requests/php/match_extended.php} +1 -1
- data/spec/fixtures/{match_extended2.php → requests/php/match_extended2.php} +1 -1
- data/spec/fixtures/{match_fullscan.php → requests/php/match_fullscan.php} +1 -1
- data/spec/fixtures/{match_phrase.php → requests/php/match_phrase.php} +1 -1
- data/spec/fixtures/{max_query_time.php → requests/php/max_query_time.php} +1 -1
- data/spec/fixtures/{miltiple_queries.php → requests/php/miltiple_queries.php} +1 -1
- data/spec/fixtures/requests/php/outer_select.php +9 -0
- data/spec/fixtures/{set_override.php → requests/php/override.php} +1 -1
- data/spec/fixtures/requests/php/query_flag.php +13 -0
- data/spec/fixtures/requests/php/query_flag_after_reset.php +19 -0
- data/spec/fixtures/{ranking_bm25.php → requests/php/ranking_bm25.php} +1 -1
- data/spec/fixtures/requests/php/ranking_expr.php +9 -0
- data/spec/fixtures/{ranking_fieldmask.php → requests/php/ranking_fieldmask.php} +1 -1
- data/spec/fixtures/{ranking_matchany.php → requests/php/ranking_matchany.php} +1 -1
- data/spec/fixtures/{ranking_none.php → requests/php/ranking_none.php} +1 -1
- data/spec/fixtures/{ranking_proximity.php → requests/php/ranking_proximity.php} +1 -1
- data/spec/fixtures/{ranking_proximity_bm25.php → requests/php/ranking_proximity_bm25.php} +1 -1
- data/spec/fixtures/{ranking_sph04.php → requests/php/ranking_sph04.php} +1 -1
- data/spec/fixtures/{ranking_wordcount.php → requests/php/ranking_wordcount.php} +1 -1
- data/spec/fixtures/{retries.php → requests/php/retries.php} +1 -1
- data/spec/fixtures/{retries_delay.php → requests/php/retries_delay.php} +1 -1
- data/spec/fixtures/{select.php → requests/php/select.php} +1 -1
- data/spec/fixtures/{sort_attr_asc.php → requests/php/sort_attr_asc.php} +1 -1
- data/spec/fixtures/{sort_attr_desc.php → requests/php/sort_attr_desc.php} +1 -1
- data/spec/fixtures/{sort_expr.php → requests/php/sort_expr.php} +1 -1
- data/spec/fixtures/{sort_extended.php → requests/php/sort_extended.php} +1 -1
- data/spec/fixtures/{sort_relevance.php → requests/php/sort_relevance.php} +1 -1
- data/spec/fixtures/{sort_time_segments.php → requests/php/sort_time_segments.php} +1 -1
- data/spec/fixtures/{update_attributes.php → requests/php/update_attributes.php} +1 -1
- data/spec/fixtures/{update_attributes_mva.php → requests/php/update_attributes_mva.php} +1 -1
- data/spec/fixtures/{weights.php → requests/php/weights.php} +1 -1
- data/spec/fixtures/requests/query_flag.dat +0 -0
- data/spec/fixtures/requests/query_flag_after_reset.dat +0 -0
- data/spec/fixtures/requests/ranking_bm25.dat +0 -0
- data/spec/fixtures/requests/ranking_expr.dat +0 -0
- data/spec/fixtures/requests/ranking_fieldmask.dat +0 -0
- data/spec/fixtures/requests/ranking_matchany.dat +0 -0
- data/spec/fixtures/requests/ranking_none.dat +0 -0
- data/spec/fixtures/requests/ranking_proximity.dat +0 -0
- data/spec/fixtures/requests/ranking_proximity_bm25.dat +0 -0
- data/spec/fixtures/requests/ranking_sph04.dat +0 -0
- data/spec/fixtures/requests/ranking_wordcount.dat +0 -0
- data/spec/fixtures/requests/retries.dat +0 -0
- data/spec/fixtures/requests/retries_delay.dat +0 -0
- data/spec/fixtures/requests/select.dat +0 -0
- data/spec/fixtures/requests/sort_attr_asc.dat +0 -0
- data/spec/fixtures/requests/sort_attr_desc.dat +0 -0
- data/spec/fixtures/requests/sort_expr.dat +0 -0
- data/spec/fixtures/requests/sort_extended.dat +0 -0
- data/spec/fixtures/requests/sort_relevance.dat +0 -0
- data/spec/fixtures/requests/sort_time_segments.dat +0 -0
- data/spec/fixtures/requests/update_attributes.dat +0 -0
- data/spec/fixtures/requests/update_attributes_mva.dat +0 -0
- data/spec/fixtures/requests/weights.dat +0 -0
- data/spec/fixtures/responses/build_excerpts.dat +0 -0
- data/spec/fixtures/responses/build_keywords.dat +0 -0
- data/spec/fixtures/responses/flush_attributes.dat +0 -0
- data/spec/fixtures/responses/flush_attrs.dat +2 -0
- data/spec/fixtures/responses/open.dat +0 -0
- data/spec/fixtures/responses/open_twice.dat +0 -0
- data/spec/fixtures/responses/php/build_excerpts.php +8 -0
- data/spec/fixtures/responses/php/build_keywords.php +8 -0
- data/spec/fixtures/responses/php/flush_attributes.php +8 -0
- data/spec/fixtures/responses/php/open.php +8 -0
- data/spec/fixtures/responses/php/open_twice.php +9 -0
- data/spec/fixtures/responses/php/query.php +8 -0
- data/spec/fixtures/responses/php/query_error.php +8 -0
- data/spec/fixtures/responses/php/query_id64.php +8 -0
- data/spec/fixtures/responses/php/run_queries.php +10 -0
- data/spec/fixtures/responses/php/run_queries_error.php +9 -0
- data/spec/fixtures/responses/php/status.php +8 -0
- data/spec/fixtures/responses/php/update_attributes.php +8 -0
- data/spec/fixtures/responses/php/update_attributes_mva.php +8 -0
- data/spec/fixtures/responses/query.dat +0 -0
- data/spec/fixtures/responses/query_error.dat +0 -0
- data/spec/fixtures/responses/query_id64.dat +0 -0
- data/spec/fixtures/responses/run_queries.dat +0 -0
- data/spec/fixtures/responses/run_queries_error.dat +0 -0
- data/spec/fixtures/responses/status.dat +0 -0
- data/spec/fixtures/responses/update_attributes.dat +0 -0
- data/spec/fixtures/responses/update_attributes_mva.dat +0 -0
- data/spec/fixtures/sphinxapi.php +217 -45
- data/spec/spec_helper.rb +18 -3
- data/spec/sphinx/sphinx-id64.conf +6 -6
- data/spec/sphinx/sphinx.conf +6 -6
- data/sphinx.gemspec +19 -121
- metadata +268 -105
- data/README.rdoc +0 -243
- data/VERSION.yml +0 -5
- data/init.rb +0 -1
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -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
data/README.md
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
# Sphinx Client API
|
2
|
+
|
3
|
+
[](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 '
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
19
|
-
|
20
|
+
require 'bundler'
|
21
|
+
Bundler::GemHelper.install_tasks
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
+
namespace :fixtures do
|
24
|
+
FIXTURES_DIR = File.expand_path('../spec/fixtures', __FILE__)
|
23
25
|
|
24
|
-
desc '
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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]
|
data/lib/sphinx.rb
CHANGED
@@ -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 —
|
4
|
+
# Copyright:: Copyright (c) 2006 — 2013 Dmytro Shteflyuk
|
5
5
|
# License:: Distributes under the same terms as Ruby
|
6
|
-
# Version:: 0.9.10
|
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"
|
data/lib/sphinx/client.rb
CHANGED
@@ -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
|
-
:
|
111
|
-
:sort_mode => { :mode => @sort, :
|
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
|
-
|
680
|
-
|
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
|
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(
|
1730
|
+
result = HashWithIndifferentAccess.new(:error => '', :warning => '')
|
1619
1731
|
|
1620
1732
|
# extract status
|
1621
|
-
status = result[
|
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[
|
1737
|
+
result[:warning] = message
|
1626
1738
|
else
|
1627
|
-
result[
|
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[
|
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 =
|
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[
|
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[
|
1651
|
-
doc
|
1652
|
-
|
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 =
|
1659
|
-
match[
|
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
|
-
|
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[
|
1683
|
-
result[
|
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[
|
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] =
|
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
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
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.
|
1779
|
-
flags
|
1780
|
-
flags |= 2
|
1781
|
-
flags |= 4
|
1782
|
-
flags |= 8
|
1783
|
-
flags |= 16
|
1784
|
-
flags |= 32
|
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[
|
1795
|
-
request.
|
1796
|
-
request.
|
1797
|
-
request.
|
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
|
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 :
|
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 |
|
2186
|
-
logger.info { "[sphinx] #{command} on server #{
|
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(
|
2193
|
-
|
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 <<
|
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
|
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
|