sunspot 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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