sunspot 2.1.0 → 2.1.1

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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/History.txt +10 -0
  4. data/lib/sunspot.rb +6 -6
  5. data/lib/sunspot/batcher.rb +1 -1
  6. data/lib/sunspot/dsl.rb +1 -1
  7. data/lib/sunspot/dsl/field_query.rb +47 -7
  8. data/lib/sunspot/dsl/field_stats.rb +18 -0
  9. data/lib/sunspot/dsl/fields.rb +10 -1
  10. data/lib/sunspot/field.rb +15 -0
  11. data/lib/sunspot/field_factory.rb +33 -0
  12. data/lib/sunspot/indexer.rb +1 -0
  13. data/lib/sunspot/query.rb +1 -1
  14. data/lib/sunspot/query/common_query.rb +5 -0
  15. data/lib/sunspot/query/field_stats.rb +28 -0
  16. data/lib/sunspot/query/geofilt.rb +8 -3
  17. data/lib/sunspot/query/restriction.rb +5 -2
  18. data/lib/sunspot/query/sort.rb +35 -0
  19. data/lib/sunspot/search.rb +2 -1
  20. data/lib/sunspot/search/abstract_search.rb +57 -35
  21. data/lib/sunspot/search/field_facet.rb +4 -4
  22. data/lib/sunspot/search/field_stats.rb +21 -0
  23. data/lib/sunspot/search/stats_facet.rb +25 -0
  24. data/lib/sunspot/search/stats_row.rb +66 -0
  25. data/lib/sunspot/session.rb +6 -6
  26. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +6 -4
  27. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +16 -8
  28. data/lib/sunspot/setup.rb +6 -0
  29. data/lib/sunspot/version.rb +1 -1
  30. data/spec/api/class_set_spec.rb +3 -3
  31. data/spec/api/indexer/fixed_fields_spec.rb +5 -0
  32. data/spec/api/indexer/removal_spec.rb +13 -3
  33. data/spec/api/query/faceting_examples.rb +19 -0
  34. data/spec/api/query/join_spec.rb +19 -0
  35. data/spec/api/query/standard_spec.rb +1 -0
  36. data/spec/api/query/stats_examples.rb +66 -0
  37. data/spec/api/search/paginated_collection_spec.rb +1 -1
  38. data/spec/api/search/stats_spec.rb +94 -0
  39. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +8 -2
  40. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +14 -2
  41. data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +3 -3
  42. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +1 -1
  43. data/spec/helpers/search_helper.rb +30 -0
  44. data/spec/integration/highlighting_spec.rb +3 -3
  45. data/spec/integration/indexing_spec.rb +3 -2
  46. data/spec/integration/scoped_search_spec.rb +30 -0
  47. data/spec/integration/stats_spec.rb +47 -0
  48. data/spec/mocks/photo.rb +14 -1
  49. data/sunspot.gemspec +1 -2
  50. data/tasks/rdoc.rake +22 -14
  51. metadata +32 -41
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7e84a7b3df6b149b54fbea2bb14a67cd561cbb71
4
+ data.tar.gz: 61c35909935859b351c2608362e733aceaf65394
5
+ SHA512:
6
+ metadata.gz: 23311e3c435b010699df2bbb6c6fe634ad64178d0a02637d9bdc500e4d5814678d6cfb45c83cad7c118f08b5d68ddae766257f91496f5db946c696f3a99efb13
7
+ data.tar.gz: 4d16d14e56f6734e1d0516794979e10d2ba8bd3719e97a295f124f58c914841df0fcf0a04929ad31516da7e8b2b0056203dd4a5af12e69d86e2e1d382c0efd71
data/Gemfile CHANGED
@@ -3,3 +3,7 @@ source "http://rubygems.org"
3
3
  gem 'sunspot_solr', :path => File.expand_path('../../sunspot_solr', __FILE__)
4
4
 
5
5
  gemspec
6
+
7
+ group :development do
8
+ gem 'rake'
9
+ end
@@ -1,3 +1,13 @@
1
+ == 2.1.0
2
+ * Dropped support for rails 2.x. Added support for Rails 4.
3
+ * Bundled Solr installation (`sunspot_solr`) is version 4.2.0
4
+ * Upgrade to Jetty 8 and changed solr config directory structure.
5
+ * Removed interactive warning prompt from sunspot:reindex rake task.
6
+ * Support Rails 4 custom primary keys i.e. UUID
7
+ * Switch to EDismax query parser from dismax.
8
+ * Extend Sunspot.remove! to accept a block, and enable .remove_by_id! to accept multiple ids.
9
+ * add 'order_by_function' method to field queries. Allowing for Solr FunctionQuery, nesting, and literal constants.
10
+
1
11
  == 2.0.0
2
12
  * Adds support for field grouping (Andy Lindeman)
3
13
  * Adds support for native geospatial searches and ordering (Eric Tang, Bruno Miranda, Andy Lindeman)
@@ -417,8 +417,8 @@ module Sunspot
417
417
  #
418
418
  # objects...<Object>:: Objects to remove from the index
419
419
  #
420
- def remove!(*objects)
421
- session.remove!(*objects)
420
+ def remove!(*objects, &block)
421
+ session.remove!(*objects, &block)
422
422
  end
423
423
 
424
424
  #
@@ -433,16 +433,16 @@ module Sunspot
433
433
  # Primary key of the object. This should be the same id that would be
434
434
  # returned by the class's instance adapter.
435
435
  #
436
- def remove_by_id(clazz, id)
437
- session.remove_by_id(clazz, id)
436
+ def remove_by_id(clazz, *ids)
437
+ session.remove_by_id(clazz, ids)
438
438
  end
439
439
 
440
440
  #
441
441
  # Remove an object by class name and primary key, and immediately commit.
442
442
  # See #remove_by_id and #commit
443
443
  #
444
- def remove_by_id!(clazz, id)
445
- session.remove_by_id!(clazz, id)
444
+ def remove_by_id!(clazz, *ids)
445
+ session.remove_by_id!(clazz, ids)
446
446
  end
447
447
 
448
448
  # Remove all objects of the given classes from the index. There isn't much
@@ -12,7 +12,7 @@ module Sunspot
12
12
  # end
13
13
  #
14
14
  # it is the Batcher's job to keep track of these nestings. The inner will
15
- # be sent of to be indexed first.
15
+ # be sent off to be indexed first.
16
16
  #
17
17
  class Batcher
18
18
  include Enumerable
@@ -1,5 +1,5 @@
1
1
  %w(fields scope paginatable adjustable field_query standard_query query_facet
2
2
  functional fulltext restriction restriction_with_near search
3
- more_like_this_query function field_group).each do |file|
3
+ more_like_this_query function field_group field_stats).each do |file|
4
4
  require File.join(File.dirname(__FILE__), 'dsl', file)
5
5
  end
@@ -1,6 +1,6 @@
1
1
  module Sunspot
2
2
  module DSL
3
- #
3
+ #
4
4
  # Provides an API for areas of the query DSL that operate on specific
5
5
  # fields. This functionality is provided by the query DSL and the dynamic
6
6
  # query DSL.
@@ -45,14 +45,14 @@ module Sunspot
45
45
  # the reference longitude
46
46
  # direction<Symbol>::
47
47
  # :asc or :desc (default :asc)
48
- #
48
+ #
49
49
  def order_by_geodist(field_name, lat, lon, direction = nil)
50
50
  @query.add_sort(
51
51
  Sunspot::Query::Sort::GeodistSort.new(@setup.field(field_name), lat, lon, direction)
52
52
  )
53
53
  end
54
54
 
55
- #
55
+ #
56
56
  # DEPRECATED Use <code>order_by(:random)</code>
57
57
  #
58
58
  def order_by_random
@@ -109,7 +109,7 @@ module Sunspot
109
109
  # with(:blog_id, 1)
110
110
  # facet(:category_id)
111
111
  # end
112
- #
112
+ #
113
113
  # The facet specified above will have a row for each category_id that is
114
114
  # present in a document which also has a blog_id of 1.
115
115
  #
@@ -125,12 +125,12 @@ module Sunspot
125
125
  # category_filter = with(:category_id, 2)
126
126
  # facet(:category_id, :exclude => category_filter)
127
127
  # end
128
- #
128
+ #
129
129
  # Although the results of the above search will be restricted to those
130
130
  # with a category_id of 2, the category_id facet will operate as if a
131
131
  # category had not been selected, allowing the user to select additional
132
132
  # categories (which will presumably be ORed together).
133
- #
133
+ #
134
134
  # It possible to exclude multiple filters by passing an array:
135
135
  #
136
136
  # Sunspot.search(Post) do
@@ -141,7 +141,7 @@ module Sunspot
141
141
  # :exclude => [category_filter, author_filter].compact)
142
142
  # end
143
143
  #
144
- # You should consider using +.compact+ to ensure that the array does not
144
+ # You should consider using +.compact+ to ensure that the array does not
145
145
  # contain any nil values.
146
146
  #
147
147
  # <strong>As far as I can tell, Solr only supports multi-select with
@@ -331,6 +331,22 @@ module Sunspot
331
331
  end
332
332
  end
333
333
 
334
+ def stats(*field_names, &block)
335
+ options = Sunspot::Util.extract_options_from(field_names)
336
+
337
+ field_names.each do |field_name|
338
+ field = @setup.field(field_name)
339
+ query_stats = @query.add_stats(
340
+ Sunspot::Query::FieldStats.new(field, options)
341
+ )
342
+ search_stats = @search.add_field_stats(field)
343
+
344
+ Sunspot::Util.instance_eval_or_call(
345
+ FieldStats.new(query_stats, @setup, search_stats),
346
+ &block) if block
347
+ end
348
+ end
349
+
334
350
  def dynamic(base_name, &block)
335
351
  dynamic_field_factory = @setup.dynamic_field_factory(base_name)
336
352
  Sunspot::Util.instance_eval_or_call(
@@ -338,6 +354,30 @@ module Sunspot
338
354
  &block
339
355
  )
340
356
  end
357
+ #
358
+ # Specify that results should be ordered based on a
359
+ # FunctionQuery - http://wiki.apache.org/solr/FunctionQuery
360
+ # Solr 3.1 and up
361
+ #
362
+ # For example, to order by field1 + (field2*field3):
363
+ #
364
+ # order_by_function :sum, :field1, [:product, :field2, :field3], :desc
365
+ #
366
+ # ==== Parameters
367
+ # function_name<Symbol>::
368
+ # the function to run
369
+ # arguments::
370
+ # the arguments for this function.
371
+ # - Symbol for a field or function name
372
+ # - Array for a nested function
373
+ # - String for a literal constant
374
+ # direction<Symbol>::
375
+ # :asc or :desc
376
+ def order_by_function(*args)
377
+ @query.add_sort(
378
+ Sunspot::Query::Sort::FunctionSort.new(@setup,args)
379
+ )
380
+ end
341
381
  end
342
382
  end
343
383
  end
@@ -0,0 +1,18 @@
1
+ module Sunspot
2
+ module DSL
3
+ class FieldStats #:nodoc:
4
+ def initialize(query_stats, setup, search_stats) #:nodoc:
5
+ @query_stats, @setup, @search_stats = query_stats, setup, search_stats
6
+ end
7
+
8
+ def facet *field_names
9
+ field_names.each do |field_name|
10
+ field = @setup.field(field_name)
11
+
12
+ @query_stats.add_facet(field)
13
+ @search_stats.add_facet(field)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -74,10 +74,17 @@ module Sunspot
74
74
  #
75
75
  def method_missing(method, *args, &block)
76
76
  options = Util.extract_options_from(args)
77
- type_const_name = "#{Util.camel_case(method.to_s.sub(/^dynamic_/, ''))}Type"
77
+ if method.to_s == 'join'
78
+ type_string = options.delete(:type).to_s
79
+ else
80
+ type_string = method.to_s
81
+ end
82
+ type_const_name = "#{Util.camel_case(type_string.sub(/^dynamic_/, ''))}Type"
78
83
  trie = options.delete(:trie)
79
84
  type_const_name = "Trie#{type_const_name}" if trie
80
85
  begin
86
+
87
+ type_class = options[:type]
81
88
  type_class = Type.const_get(type_const_name)
82
89
  rescue(NameError)
83
90
  if trie
@@ -94,6 +101,8 @@ module Sunspot
94
101
  else
95
102
  super(method, *args, &block)
96
103
  end
104
+ elsif method.to_s == 'join'
105
+ @setup.add_join_field_factory(name, type, options, &block)
97
106
  else
98
107
  @setup.add_field_factory(name, type, options, &block)
99
108
  end
@@ -159,6 +159,21 @@ module Sunspot
159
159
 
160
160
  end
161
161
 
162
+ class JoinField < Field #:nodoc:
163
+
164
+ def initialize(name, type, options = {})
165
+ @multiple = !!options.delete(:multiple)
166
+ super(name, type, options)
167
+ @join_string = options.delete(:join_string)
168
+ raise ArgumentError, "Unknown field option #{options.keys.first.inspect} provided for field #{name.inspect}" unless options.empty?
169
+ end
170
+
171
+ def local_params
172
+ "{!join #{@join_string}}"
173
+ end
174
+
175
+ end
176
+
162
177
  class TypeField #:nodoc:
163
178
  class <<self
164
179
  def instance
@@ -76,6 +76,39 @@ module Sunspot
76
76
  end
77
77
  end
78
78
 
79
+ class Join < Abstract
80
+ def initialize(name, type, options = {}, &block)
81
+ super(name, options, &block)
82
+ unless name.to_s =~ /^\w+$/
83
+ raise ArgumentError, "Invalid field name #{name}: only letters, numbers, and underscores are allowed."
84
+ end
85
+ @field =
86
+ JoinField.new(name, type, options)
87
+ end
88
+
89
+ #
90
+ # Return the field instance built by this factory
91
+ #
92
+ def build
93
+ @field
94
+ end
95
+
96
+ #
97
+ # Extract the encapsulated field's data from the given model and add it
98
+ # into the Solr document for indexing. (noop here for joins)
99
+ #
100
+ def populate_document(document, model) #:nodoc:
101
+
102
+ end
103
+
104
+ #
105
+ # A unique signature identifying this field by name and type.
106
+ #
107
+ def signature
108
+ ['join', @field.name, @field.type]
109
+ end
110
+ end
111
+
79
112
  #
80
113
  # DynamicFieldFactories create dynamic field instances based on dynamic
81
114
  # configuration.
@@ -44,6 +44,7 @@ module Sunspot
44
44
  # Remove the model from the Solr index by specifying the class and ID
45
45
  #
46
46
  def remove_by_id(class_name, *ids)
47
+ ids.flatten!
47
48
  @connection.delete_by_id(
48
49
  ids.map { |id| Adapters::InstanceAdapter.index_id_for(class_name, id) }
49
50
  )
@@ -1,7 +1,7 @@
1
1
  %w(filter abstract_field_facet connective boost_query date_field_facet range_facet dismax
2
2
  field_facet highlighting pagination restriction common_query
3
3
  standard_query more_like_this more_like_this_query geo geofilt bbox query_facet
4
- scope sort sort_composite text_field_boost function_query
4
+ scope sort sort_composite text_field_boost function_query field_stats
5
5
  composite_fulltext field_group).each do |file|
6
6
  require(File.join(File.dirname(__FILE__), 'query', file))
7
7
  end
@@ -48,6 +48,11 @@ module Sunspot
48
48
  geo
49
49
  end
50
50
 
51
+ def add_stats(stats)
52
+ @components << stats
53
+ stats
54
+ end
55
+
51
56
  def paginate(page, per_page, offset = nil)
52
57
  if @pagination
53
58
  @pagination.offset = offset
@@ -0,0 +1,28 @@
1
+ module Sunspot
2
+ module Query
3
+ class FieldStats
4
+ def initialize(field, options)
5
+ @field, @options = field, options
6
+ @facets = []
7
+ end
8
+
9
+ def add_facet field
10
+ @facets << field
11
+ end
12
+
13
+ def to_params
14
+ params = { :stats => true, :"stats.field" => [@field.indexed_name]}
15
+ params[facet_key] = @facets.map(&:indexed_name) unless @facets.empty?
16
+ params
17
+ end
18
+
19
+ def facet_key
20
+ qualified_param 'facet'
21
+ end
22
+
23
+ def qualified_param name
24
+ :"f.#{@field.indexed_name}.stats.#{name}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,15 +1,20 @@
1
1
  module Sunspot
2
2
  module Query
3
3
  class Geofilt
4
+ include Filter
5
+ attr_reader :field
6
+
4
7
  def initialize(field, lat, lon, radius, options = {})
5
8
  @field, @lat, @lon, @radius, @options = field, lat, lon, radius, options
6
9
  end
7
10
 
8
- def to_params
11
+ def to_boolean_phrase
9
12
  func = @options[:bbox] ? "bbox" : "geofilt"
13
+ "{!#{func} sfield=#{@field.indexed_name} pt=#{@lat},#{@lon} d=#{@radius}}"
14
+ end
10
15
 
11
- filter = "{!#{func} sfield=#{@field.indexed_name} pt=#{@lat},#{@lon} d=#{@radius}}"
12
- {:fq => filter}
16
+ def to_params
17
+ {:fq => to_filter_query}
13
18
  end
14
19
  end
15
20
  end
@@ -68,11 +68,14 @@ module Sunspot
68
68
  # on whether this restriction is negated.
69
69
  #
70
70
  def to_boolean_phrase
71
+ phrase = []
72
+ phrase << @field.local_params if @field.respond_to? :local_params
71
73
  unless negated?
72
- to_positive_boolean_phrase
74
+ phrase << to_positive_boolean_phrase
73
75
  else
74
- to_negated_boolean_phrase
76
+ phrase << to_negated_boolean_phrase
75
77
  end
78
+ phrase.join
76
79
  end
77
80
 
78
81
  #
@@ -114,6 +114,41 @@ module Sunspot
114
114
  "geodist(#{@field.indexed_name.to_sym},#{@lat},#{@lon}) #{direction_for_solr}"
115
115
  end
116
116
  end
117
+
118
+ #
119
+ # A FunctionSort sorts by solr function.
120
+ # FunctionComp recursively parses arguments for nesting
121
+ #
122
+ class FunctionSort < Abstract
123
+ attr_reader :comp
124
+ def initialize(setup,args)
125
+ @direction = args.pop
126
+ @comp = FunctionComp.new(setup,args)
127
+ end
128
+ def to_param
129
+ "#{comp.to_s} #{direction_for_solr}"
130
+ end
131
+ end
132
+ class FunctionComp
133
+ attr_accessor :function,:fields
134
+ def initialize(setup,args)
135
+ @function=args.shift
136
+ @fields = []
137
+ args.each do |argument|
138
+ case argument.class.name
139
+ when "Array"
140
+ @fields<< FunctionComp.new(setup,argument)
141
+ when "Symbol"
142
+ @fields<< setup.field(argument).indexed_name
143
+ else
144
+ @fields<< argument
145
+ end
146
+ end
147
+ end
148
+ def to_s
149
+ "#{function}(#{fields.map(&:to_s).join(",")})"
150
+ end
151
+ end
117
152
  end
118
153
  end
119
154
  end