sunspot 2.1.0 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/History.txt +10 -0
- data/lib/sunspot.rb +6 -6
- data/lib/sunspot/batcher.rb +1 -1
- data/lib/sunspot/dsl.rb +1 -1
- data/lib/sunspot/dsl/field_query.rb +47 -7
- data/lib/sunspot/dsl/field_stats.rb +18 -0
- data/lib/sunspot/dsl/fields.rb +10 -1
- data/lib/sunspot/field.rb +15 -0
- data/lib/sunspot/field_factory.rb +33 -0
- data/lib/sunspot/indexer.rb +1 -0
- data/lib/sunspot/query.rb +1 -1
- data/lib/sunspot/query/common_query.rb +5 -0
- data/lib/sunspot/query/field_stats.rb +28 -0
- data/lib/sunspot/query/geofilt.rb +8 -3
- data/lib/sunspot/query/restriction.rb +5 -2
- data/lib/sunspot/query/sort.rb +35 -0
- data/lib/sunspot/search.rb +2 -1
- data/lib/sunspot/search/abstract_search.rb +57 -35
- data/lib/sunspot/search/field_facet.rb +4 -4
- data/lib/sunspot/search/field_stats.rb +21 -0
- data/lib/sunspot/search/stats_facet.rb +25 -0
- data/lib/sunspot/search/stats_row.rb +66 -0
- data/lib/sunspot/session.rb +6 -6
- data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +6 -4
- data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +16 -8
- data/lib/sunspot/setup.rb +6 -0
- data/lib/sunspot/version.rb +1 -1
- data/spec/api/class_set_spec.rb +3 -3
- data/spec/api/indexer/fixed_fields_spec.rb +5 -0
- data/spec/api/indexer/removal_spec.rb +13 -3
- data/spec/api/query/faceting_examples.rb +19 -0
- data/spec/api/query/join_spec.rb +19 -0
- data/spec/api/query/standard_spec.rb +1 -0
- data/spec/api/query/stats_examples.rb +66 -0
- data/spec/api/search/paginated_collection_spec.rb +1 -1
- data/spec/api/search/stats_spec.rb +94 -0
- data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +8 -2
- data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +14 -2
- data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +3 -3
- data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +1 -1
- data/spec/helpers/search_helper.rb +30 -0
- data/spec/integration/highlighting_spec.rb +3 -3
- data/spec/integration/indexing_spec.rb +3 -2
- data/spec/integration/scoped_search_spec.rb +30 -0
- data/spec/integration/stats_spec.rb +47 -0
- data/spec/mocks/photo.rb +14 -1
- data/sunspot.gemspec +1 -2
- data/tasks/rdoc.rake +22 -14
- metadata +32 -41
data/lib/sunspot/search.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
%w(abstract_search standard_search more_like_this_search query_facet field_facet
|
2
|
-
date_facet range_facet facet_row hit highlight field_group group hit_enumerable
|
2
|
+
date_facet range_facet facet_row hit highlight field_group group hit_enumerable
|
3
|
+
stats_row field_stats stats_facet).each do |file|
|
3
4
|
require File.join(File.dirname(__FILE__), 'search', file)
|
4
5
|
end
|
5
6
|
|
@@ -3,24 +3,24 @@ require 'sunspot/search/hit_enumerable'
|
|
3
3
|
|
4
4
|
module Sunspot
|
5
5
|
module Search #:nodoc:
|
6
|
-
|
7
|
-
#
|
6
|
+
|
7
|
+
#
|
8
8
|
# This class encapsulates the results of a Solr search. It provides access
|
9
9
|
# to search results, total result count, facets, and pagination information.
|
10
10
|
# Instances of Search are returned by the Sunspot.search and
|
11
11
|
# Sunspot.new_search methods.
|
12
12
|
#
|
13
13
|
class AbstractSearch
|
14
|
-
#
|
14
|
+
#
|
15
15
|
# Retrieve all facet objects defined for this search, in order they were
|
16
16
|
# defined. To retrieve an individual facet by name, use #facet()
|
17
17
|
#
|
18
|
-
attr_reader :facets, :groups
|
18
|
+
attr_reader :facets, :groups, :stats
|
19
19
|
attr_reader :query #:nodoc:
|
20
20
|
attr_accessor :request_handler
|
21
21
|
|
22
22
|
include HitEnumerable
|
23
|
-
|
23
|
+
|
24
24
|
def initialize(connection, setup, query, configuration) #:nodoc:
|
25
25
|
@connection, @setup, @query = connection, setup, query
|
26
26
|
@query.paginate(1, configuration.pagination.default_per_page)
|
@@ -30,8 +30,11 @@ module Sunspot
|
|
30
30
|
|
31
31
|
@groups_by_name = {}
|
32
32
|
@groups = []
|
33
|
+
|
34
|
+
@stats_by_name = {}
|
35
|
+
@stats = []
|
33
36
|
end
|
34
|
-
|
37
|
+
|
35
38
|
#
|
36
39
|
# Execute the search on the Solr instance and store the results. If you
|
37
40
|
# use Sunspot#search() to construct your searches, there is no need to call
|
@@ -49,8 +52,8 @@ module Sunspot
|
|
49
52
|
def execute! #:nodoc: deprecated
|
50
53
|
execute
|
51
54
|
end
|
52
|
-
|
53
|
-
#
|
55
|
+
|
56
|
+
#
|
54
57
|
# Get the collection of results as instantiated objects. If WillPaginate is
|
55
58
|
# available, the results will be a WillPaginate::Collection instance; if
|
56
59
|
# not, it will be a vanilla Array.
|
@@ -65,8 +68,8 @@ module Sunspot
|
|
65
68
|
def results
|
66
69
|
@results ||= paginate_collection(verified_hits.map { |hit| hit.instance })
|
67
70
|
end
|
68
|
-
|
69
|
-
#
|
71
|
+
|
72
|
+
#
|
70
73
|
# Access raw Solr result information. Returns a collection of Hit objects
|
71
74
|
# that contain the class name, primary key, keyword relevance score (if
|
72
75
|
# applicable), and any stored fields.
|
@@ -92,7 +95,7 @@ module Sunspot
|
|
92
95
|
end
|
93
96
|
alias_method :raw_results, :hits
|
94
97
|
|
95
|
-
#
|
98
|
+
#
|
96
99
|
# The total number of documents matching the query parameters
|
97
100
|
#
|
98
101
|
# ==== Returns
|
@@ -102,8 +105,8 @@ module Sunspot
|
|
102
105
|
def total
|
103
106
|
@total ||= solr_response['numFound'] || 0
|
104
107
|
end
|
105
|
-
|
106
|
-
#
|
108
|
+
|
109
|
+
#
|
107
110
|
# The time elapsed to generate the Solr response
|
108
111
|
#
|
109
112
|
# ==== Returns
|
@@ -113,8 +116,8 @@ module Sunspot
|
|
113
116
|
def query_time
|
114
117
|
@query_time ||= solr_response_header['QTime']
|
115
118
|
end
|
116
|
-
|
117
|
-
#
|
119
|
+
|
120
|
+
#
|
118
121
|
# Get the facet object for the given name. `name` can either be the name
|
119
122
|
# given to a query facet, or the field name of a field facet. Returns a
|
120
123
|
# Sunspot::Facet object.
|
@@ -164,28 +167,38 @@ module Sunspot
|
|
164
167
|
end
|
165
168
|
end
|
166
169
|
|
170
|
+
def stats(name)
|
171
|
+
if name
|
172
|
+
@stats_by_name[name.to_sym]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
167
176
|
def group(name)
|
168
177
|
if name
|
169
178
|
@groups_by_name[name.to_sym]
|
170
179
|
end
|
171
180
|
end
|
172
|
-
|
173
|
-
#
|
181
|
+
|
182
|
+
#
|
174
183
|
# Deprecated in favor of optional second argument to #facet
|
175
184
|
#
|
176
185
|
def dynamic_facet(base_name, dynamic_name) #:nodoc:
|
177
186
|
facet(base_name, dynamic_name)
|
178
187
|
end
|
179
|
-
|
188
|
+
|
180
189
|
def facet_response #:nodoc:
|
181
190
|
@solr_result['facet_counts']
|
182
191
|
end
|
183
192
|
|
193
|
+
def stats_response #:nodoc:
|
194
|
+
@solr_result['stats']['stats_fields']
|
195
|
+
end
|
196
|
+
|
184
197
|
def group_response #:nodoc:
|
185
198
|
@solr_result['grouped']
|
186
199
|
end
|
187
|
-
|
188
|
-
#
|
200
|
+
|
201
|
+
#
|
189
202
|
# Build this search using a DSL block. This method can be called more than
|
190
203
|
# once on an unexecuted search (e.g., Sunspot.new_search) in order to build
|
191
204
|
# a search incrementally.
|
@@ -202,8 +215,8 @@ module Sunspot
|
|
202
215
|
Util.instance_eval_or_call(dsl, &block)
|
203
216
|
self
|
204
217
|
end
|
205
|
-
|
206
|
-
|
218
|
+
|
219
|
+
|
207
220
|
def inspect #:nodoc:
|
208
221
|
"<Sunspot::Search:#{query.to_params.inspect}>"
|
209
222
|
end
|
@@ -211,16 +224,16 @@ module Sunspot
|
|
211
224
|
def add_field_group(field) #:nodoc:
|
212
225
|
add_group(field.name, FieldGroup.new(field, self))
|
213
226
|
end
|
214
|
-
|
227
|
+
|
215
228
|
def add_field_facet(field, options = {}) #:nodoc:
|
216
229
|
name = (options[:name] || field.name)
|
217
230
|
add_facet(name, FieldFacet.new(field, self, options))
|
218
231
|
end
|
219
|
-
|
232
|
+
|
220
233
|
def add_query_facet(name, options) #:nodoc:
|
221
234
|
add_facet(name, QueryFacet.new(name, self, options))
|
222
235
|
end
|
223
|
-
|
236
|
+
|
224
237
|
def add_date_facet(field, options) #:nodoc:
|
225
238
|
name = (options[:name] || field.name)
|
226
239
|
add_facet(name, DateFacet.new(field, self, options))
|
@@ -231,26 +244,30 @@ module Sunspot
|
|
231
244
|
add_facet(name, RangeFacet.new(field, self, options))
|
232
245
|
end
|
233
246
|
|
247
|
+
def add_field_stats(field) #:nodoc:
|
248
|
+
add_stats(field.name, FieldStats.new(field, self))
|
249
|
+
end
|
250
|
+
|
234
251
|
def highlights_for(doc) #:nodoc:
|
235
252
|
if @solr_result['highlighting']
|
236
253
|
@solr_result['highlighting'][doc['id']]
|
237
254
|
end
|
238
255
|
end
|
239
|
-
|
256
|
+
|
240
257
|
private
|
241
|
-
|
258
|
+
|
242
259
|
def dsl
|
243
|
-
raise NotImplementedError
|
260
|
+
raise NotImplementedError
|
244
261
|
end
|
245
|
-
|
262
|
+
|
246
263
|
def execute_request(params)
|
247
264
|
raise NotImplementedError
|
248
265
|
end
|
249
|
-
|
266
|
+
|
250
267
|
def solr_response
|
251
268
|
@solr_response ||= @solr_result['response'] || {}
|
252
269
|
end
|
253
|
-
|
270
|
+
|
254
271
|
def solr_response_header
|
255
272
|
@solr_response_header ||= @solr_result['responseHeader'] || {}
|
256
273
|
end
|
@@ -258,25 +275,30 @@ module Sunspot
|
|
258
275
|
def solr_docs
|
259
276
|
solr_response['docs']
|
260
277
|
end
|
261
|
-
|
278
|
+
|
262
279
|
def verified_hits
|
263
280
|
@verified_hits ||= paginate_collection(super)
|
264
281
|
end
|
265
|
-
|
282
|
+
|
266
283
|
def paginate_collection(collection)
|
267
284
|
PaginatedCollection.new(collection, @query.page, @query.per_page, total)
|
268
285
|
end
|
269
|
-
|
286
|
+
|
270
287
|
def add_facet(name, facet)
|
271
288
|
@facets << facet
|
272
289
|
@facets_by_name[name.to_sym] = facet
|
273
290
|
end
|
274
291
|
|
292
|
+
def add_stats(name, stats)
|
293
|
+
@stats << stats
|
294
|
+
@stats_by_name[name.to_sym] = stats
|
295
|
+
end
|
296
|
+
|
275
297
|
def add_group(name, group)
|
276
298
|
@groups << group
|
277
299
|
@groups_by_name[name.to_sym] = group
|
278
300
|
end
|
279
|
-
|
301
|
+
|
280
302
|
# Clear out all the cached ivars so the search can be called again.
|
281
303
|
def reset
|
282
304
|
@results = @hits = @verified_hits = @total = @solr_response = @doc_ids = nil
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Sunspot
|
2
2
|
module Search
|
3
|
-
#
|
3
|
+
#
|
4
4
|
# A FieldFacet is a facet whose rows are all values for a certain field, in
|
5
5
|
# contrast to a QueryFacet, whose rows represent arbitrary queries.
|
6
6
|
#
|
@@ -14,7 +14,7 @@ module Sunspot
|
|
14
14
|
@field.name
|
15
15
|
end
|
16
16
|
|
17
|
-
#
|
17
|
+
#
|
18
18
|
# Get the rows returned for this facet.
|
19
19
|
#
|
20
20
|
# ==== Options (options)
|
@@ -50,7 +50,7 @@ module Sunspot
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
#
|
53
|
+
#
|
54
54
|
# If this facet references a model class, populate the rows with instances
|
55
55
|
# of the model class by loading them out of the appropriate adapter.
|
56
56
|
#
|
@@ -79,7 +79,7 @@ module Sunspot
|
|
79
79
|
rows
|
80
80
|
end
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
def key
|
84
84
|
@key ||= (@options[:name] || @field.indexed_name).to_s
|
85
85
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Search
|
3
|
+
class FieldStats < StatsRow
|
4
|
+
def initialize(field, search) #:nodoc:
|
5
|
+
@field, @search, @facet_fields = field, search, []
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_facet field
|
9
|
+
@facet_fields << field
|
10
|
+
end
|
11
|
+
|
12
|
+
def field_name
|
13
|
+
@field.name
|
14
|
+
end
|
15
|
+
|
16
|
+
def data
|
17
|
+
@search.stats_response[@field.indexed_name]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Search
|
3
|
+
class StatsFacet < FieldFacet
|
4
|
+
attr_reader :field
|
5
|
+
|
6
|
+
def initialize(field, data) #:nodoc:
|
7
|
+
@field, @data = field, data
|
8
|
+
end
|
9
|
+
|
10
|
+
def rows(options = {})
|
11
|
+
if options[:verified]
|
12
|
+
verified_rows
|
13
|
+
else
|
14
|
+
@rows ||= @data.map do |value, data|
|
15
|
+
StatsRow.new(data, self, @field.type.cast(value))
|
16
|
+
end.sort_by { |row| row.value.to_s }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def inspect
|
21
|
+
"<Sunspot::Search::StatsFacet:#{@field}>"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Search
|
3
|
+
class StatsRow
|
4
|
+
attr_reader :data, :value
|
5
|
+
attr_writer :instance #:nodoc:
|
6
|
+
|
7
|
+
def initialize(data, facet = nil, value = nil) #:nodoc:
|
8
|
+
@data, @facet, @value = data, facet, value
|
9
|
+
@facet_fields = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def min
|
13
|
+
data['min']
|
14
|
+
end
|
15
|
+
|
16
|
+
def max
|
17
|
+
data['max']
|
18
|
+
end
|
19
|
+
|
20
|
+
def count
|
21
|
+
data['count']
|
22
|
+
end
|
23
|
+
|
24
|
+
def sum
|
25
|
+
data['sum']
|
26
|
+
end
|
27
|
+
|
28
|
+
def missing
|
29
|
+
data['missing']
|
30
|
+
end
|
31
|
+
|
32
|
+
def sum_of_squares
|
33
|
+
data['sumOfSquares']
|
34
|
+
end
|
35
|
+
|
36
|
+
def mean
|
37
|
+
data['mean']
|
38
|
+
end
|
39
|
+
|
40
|
+
def standard_deviation
|
41
|
+
data['stddev']
|
42
|
+
end
|
43
|
+
|
44
|
+
def facet name
|
45
|
+
facets.find { |facet| facet.field.name == name.to_sym }
|
46
|
+
end
|
47
|
+
|
48
|
+
def facets
|
49
|
+
@facets ||= @facet_fields.map do |field|
|
50
|
+
StatsFacet.new(field, data['facets'][field.indexed_name])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def instance
|
55
|
+
if !defined?(@instance)
|
56
|
+
@facet.populate_instances
|
57
|
+
end
|
58
|
+
@instance
|
59
|
+
end
|
60
|
+
|
61
|
+
def inspect
|
62
|
+
"<Sunspot::Search::StatsRow:#{value.inspect} min=#{min} max=#{max} count=#{count}>"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/sunspot/session.rb
CHANGED
@@ -142,29 +142,29 @@ module Sunspot
|
|
142
142
|
#
|
143
143
|
# See Sunspot.remove!
|
144
144
|
#
|
145
|
-
def remove!(*objects)
|
146
|
-
remove(*objects)
|
145
|
+
def remove!(*objects, &block)
|
146
|
+
remove(*objects, &block)
|
147
147
|
commit
|
148
148
|
end
|
149
149
|
|
150
150
|
#
|
151
151
|
# See Sunspot.remove_by_id
|
152
152
|
#
|
153
|
-
def remove_by_id(clazz,
|
153
|
+
def remove_by_id(clazz, *ids)
|
154
154
|
class_name =
|
155
155
|
if clazz.is_a?(Class)
|
156
156
|
clazz.name
|
157
157
|
else
|
158
158
|
clazz.to_s
|
159
159
|
end
|
160
|
-
indexer.remove_by_id(class_name,
|
160
|
+
indexer.remove_by_id(class_name, ids)
|
161
161
|
end
|
162
162
|
|
163
163
|
#
|
164
164
|
# See Sunspot.remove_by_id!
|
165
165
|
#
|
166
|
-
def remove_by_id!(clazz,
|
167
|
-
remove_by_id(clazz,
|
166
|
+
def remove_by_id!(clazz, *ids)
|
167
|
+
remove_by_id(clazz, ids)
|
168
168
|
commit
|
169
169
|
end
|
170
170
|
|
@@ -23,15 +23,17 @@ module Sunspot
|
|
23
23
|
#
|
24
24
|
# See Sunspot.remove_by_id
|
25
25
|
#
|
26
|
-
def remove_by_id(clazz,
|
27
|
-
|
26
|
+
def remove_by_id(clazz, *ids)
|
27
|
+
ids.flatten!
|
28
|
+
session_for_class(clazz).remove_by_id(clazz, ids)
|
28
29
|
end
|
29
30
|
|
30
31
|
#
|
31
32
|
# See Sunspot.remove_by_id!
|
32
33
|
#
|
33
|
-
def remove_by_id!(clazz,
|
34
|
-
|
34
|
+
def remove_by_id!(clazz, *ids)
|
35
|
+
ids.flatten!
|
36
|
+
session_for_class(clazz).remove_by_id!(clazz, ids)
|
35
37
|
end
|
36
38
|
|
37
39
|
#
|