sunspot 2.0.0 → 2.5.0

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 (165) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +2 -0
  4. data/Appraisals +7 -0
  5. data/Gemfile +0 -2
  6. data/History.txt +10 -0
  7. data/lib/sunspot.rb +55 -17
  8. data/lib/sunspot/adapters.rb +68 -18
  9. data/lib/sunspot/batcher.rb +1 -1
  10. data/lib/sunspot/configuration.rb +4 -2
  11. data/lib/sunspot/data_extractor.rb +36 -6
  12. data/lib/sunspot/dsl.rb +4 -3
  13. data/lib/sunspot/dsl/adjustable.rb +2 -2
  14. data/lib/sunspot/dsl/field_query.rb +69 -16
  15. data/lib/sunspot/dsl/field_stats.rb +25 -0
  16. data/lib/sunspot/dsl/fields.rb +28 -8
  17. data/lib/sunspot/dsl/fulltext.rb +9 -1
  18. data/lib/sunspot/dsl/group.rb +118 -0
  19. data/lib/sunspot/dsl/paginatable.rb +4 -1
  20. data/lib/sunspot/dsl/scope.rb +19 -10
  21. data/lib/sunspot/dsl/search.rb +1 -1
  22. data/lib/sunspot/dsl/spellcheckable.rb +14 -0
  23. data/lib/sunspot/dsl/standard_query.rb +63 -35
  24. data/lib/sunspot/field.rb +76 -4
  25. data/lib/sunspot/field_factory.rb +60 -11
  26. data/lib/sunspot/indexer.rb +70 -18
  27. data/lib/sunspot/query.rb +5 -4
  28. data/lib/sunspot/query/abstract_field_facet.rb +0 -2
  29. data/lib/sunspot/query/abstract_fulltext.rb +76 -0
  30. data/lib/sunspot/query/abstract_json_field_facet.rb +70 -0
  31. data/lib/sunspot/query/bbox.rb +5 -1
  32. data/lib/sunspot/query/common_query.rb +31 -6
  33. data/lib/sunspot/query/composite_fulltext.rb +58 -8
  34. data/lib/sunspot/query/date_field_json_facet.rb +25 -0
  35. data/lib/sunspot/query/dismax.rb +25 -71
  36. data/lib/sunspot/query/field_json_facet.rb +19 -0
  37. data/lib/sunspot/query/field_list.rb +15 -0
  38. data/lib/sunspot/query/field_stats.rb +61 -0
  39. data/lib/sunspot/query/function_query.rb +1 -2
  40. data/lib/sunspot/query/geo.rb +1 -1
  41. data/lib/sunspot/query/geofilt.rb +8 -3
  42. data/lib/sunspot/query/group.rb +46 -0
  43. data/lib/sunspot/query/group_query.rb +17 -0
  44. data/lib/sunspot/query/join.rb +88 -0
  45. data/lib/sunspot/query/more_like_this.rb +1 -1
  46. data/lib/sunspot/query/pagination.rb +12 -4
  47. data/lib/sunspot/query/range_json_facet.rb +28 -0
  48. data/lib/sunspot/query/restriction.rb +99 -13
  49. data/lib/sunspot/query/sort.rb +41 -0
  50. data/lib/sunspot/query/sort_composite.rb +7 -0
  51. data/lib/sunspot/query/spellcheck.rb +19 -0
  52. data/lib/sunspot/query/standard_query.rb +24 -2
  53. data/lib/sunspot/query/text_field_boost.rb +1 -3
  54. data/lib/sunspot/schema.rb +12 -3
  55. data/lib/sunspot/search.rb +4 -2
  56. data/lib/sunspot/search/abstract_search.rb +93 -43
  57. data/lib/sunspot/search/cursor_paginated_collection.rb +32 -0
  58. data/lib/sunspot/search/field_facet.rb +4 -4
  59. data/lib/sunspot/search/field_json_facet.rb +33 -0
  60. data/lib/sunspot/search/field_stats.rb +21 -0
  61. data/lib/sunspot/search/hit.rb +6 -1
  62. data/lib/sunspot/search/hit_enumerable.rb +4 -1
  63. data/lib/sunspot/search/json_facet_row.rb +40 -0
  64. data/lib/sunspot/search/json_facet_stats.rb +23 -0
  65. data/lib/sunspot/search/paginated_collection.rb +1 -0
  66. data/lib/sunspot/search/query_group.rb +74 -0
  67. data/lib/sunspot/search/standard_search.rb +70 -3
  68. data/lib/sunspot/search/stats_facet.rb +25 -0
  69. data/lib/sunspot/search/stats_json_row.rb +82 -0
  70. data/lib/sunspot/search/stats_row.rb +68 -0
  71. data/lib/sunspot/session.rb +62 -37
  72. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +6 -4
  73. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +16 -8
  74. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +2 -2
  75. data/lib/sunspot/session_proxy/retry_5xx_session_proxy.rb +1 -1
  76. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +4 -2
  77. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +1 -1
  78. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +6 -4
  79. data/lib/sunspot/setup.rb +42 -0
  80. data/lib/sunspot/type.rb +20 -0
  81. data/lib/sunspot/util.rb +78 -14
  82. data/lib/sunspot/version.rb +1 -1
  83. data/spec/api/adapters_spec.rb +40 -15
  84. data/spec/api/batcher_spec.rb +15 -15
  85. data/spec/api/binding_spec.rb +3 -3
  86. data/spec/api/class_set_spec.rb +6 -6
  87. data/spec/api/data_extractor_spec.rb +39 -0
  88. data/spec/api/hit_enumerable_spec.rb +32 -9
  89. data/spec/api/indexer/attributes_spec.rb +35 -30
  90. data/spec/api/indexer/batch_spec.rb +8 -7
  91. data/spec/api/indexer/dynamic_fields_spec.rb +8 -8
  92. data/spec/api/indexer/fixed_fields_spec.rb +16 -11
  93. data/spec/api/indexer/fulltext_spec.rb +8 -8
  94. data/spec/api/indexer/removal_spec.rb +24 -14
  95. data/spec/api/indexer_spec.rb +2 -2
  96. data/spec/api/query/advanced_manipulation_examples.rb +3 -3
  97. data/spec/api/query/connectives_examples.rb +26 -14
  98. data/spec/api/query/dsl_spec.rb +24 -6
  99. data/spec/api/query/dynamic_fields_examples.rb +18 -18
  100. data/spec/api/query/faceting_examples.rb +80 -61
  101. data/spec/api/query/fulltext_examples.rb +194 -40
  102. data/spec/api/query/function_spec.rb +116 -13
  103. data/spec/api/query/geo_examples.rb +8 -12
  104. data/spec/api/query/group_spec.rb +27 -5
  105. data/spec/api/query/highlighting_examples.rb +26 -26
  106. data/spec/api/query/join_spec.rb +19 -0
  107. data/spec/api/query/more_like_this_spec.rb +40 -27
  108. data/spec/api/query/ordering_pagination_examples.rb +37 -23
  109. data/spec/api/query/scope_examples.rb +39 -39
  110. data/spec/api/query/spatial_examples.rb +3 -3
  111. data/spec/api/query/spellcheck_examples.rb +20 -0
  112. data/spec/api/query/standard_spec.rb +3 -1
  113. data/spec/api/query/stats_examples.rb +66 -0
  114. data/spec/api/query/text_field_scoping_examples.rb +5 -5
  115. data/spec/api/query/types_spec.rb +4 -4
  116. data/spec/api/search/cursor_paginated_collection_spec.rb +35 -0
  117. data/spec/api/search/dynamic_fields_spec.rb +4 -4
  118. data/spec/api/search/faceting_spec.rb +55 -52
  119. data/spec/api/search/highlighting_spec.rb +7 -7
  120. data/spec/api/search/hits_spec.rb +43 -29
  121. data/spec/api/search/paginated_collection_spec.rb +19 -18
  122. data/spec/api/search/results_spec.rb +13 -13
  123. data/spec/api/search/search_spec.rb +3 -3
  124. data/spec/api/search/stats_spec.rb +94 -0
  125. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +23 -16
  126. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +16 -4
  127. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +10 -6
  128. data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +11 -11
  129. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +15 -14
  130. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +3 -3
  131. data/spec/api/session_proxy/spec_helper.rb +1 -1
  132. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +40 -26
  133. data/spec/api/session_spec.rb +78 -38
  134. data/spec/api/sunspot_spec.rb +7 -4
  135. data/spec/helpers/integration_helper.rb +11 -1
  136. data/spec/helpers/query_helper.rb +1 -1
  137. data/spec/helpers/search_helper.rb +30 -0
  138. data/spec/integration/atomic_updates_spec.rb +58 -0
  139. data/spec/integration/dynamic_fields_spec.rb +31 -20
  140. data/spec/integration/faceting_spec.rb +252 -39
  141. data/spec/integration/field_grouping_spec.rb +47 -15
  142. data/spec/integration/field_lists_spec.rb +57 -0
  143. data/spec/integration/geospatial_spec.rb +34 -8
  144. data/spec/integration/highlighting_spec.rb +8 -8
  145. data/spec/integration/indexing_spec.rb +7 -6
  146. data/spec/integration/join_spec.rb +45 -0
  147. data/spec/integration/keyword_search_spec.rb +68 -38
  148. data/spec/integration/local_search_spec.rb +4 -4
  149. data/spec/integration/more_like_this_spec.rb +7 -7
  150. data/spec/integration/scoped_search_spec.rb +193 -74
  151. data/spec/integration/spellcheck_spec.rb +119 -0
  152. data/spec/integration/stats_spec.rb +88 -0
  153. data/spec/integration/stored_fields_spec.rb +1 -1
  154. data/spec/integration/test_pagination.rb +4 -4
  155. data/spec/integration/unicode_spec.rb +1 -1
  156. data/spec/mocks/adapters.rb +36 -0
  157. data/spec/mocks/connection.rb +5 -3
  158. data/spec/mocks/photo.rb +32 -1
  159. data/spec/mocks/post.rb +18 -3
  160. data/spec/spec_helper.rb +13 -8
  161. data/sunspot.gemspec +6 -4
  162. data/tasks/rdoc.rake +22 -14
  163. metadata +101 -44
  164. data/lib/sunspot/dsl/field_group.rb +0 -57
  165. data/lib/sunspot/query/field_group.rb +0 -37
@@ -0,0 +1,19 @@
1
+ module Sunspot
2
+ module Query
3
+ class FieldJsonFacet < AbstractJsonFieldFacet
4
+
5
+ def initialize(field, options, setup)
6
+ super
7
+ end
8
+
9
+ def field_name_with_local_params
10
+ {
11
+ @field.name => {
12
+ type: 'terms',
13
+ field: @field.indexed_name,
14
+ }.merge!(init_params)
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module Sunspot
2
+ module Query #:nodoc:
3
+
4
+ # This DSL represents only fields that would come out of the results of the search type.
5
+ class FieldList
6
+ def initialize(list)
7
+ @list = list
8
+ end
9
+
10
+ def to_params
11
+ { :fl => @list }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,61 @@
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 add_json_facet(json_facet)
14
+ @json_facet = json_facet
15
+ end
16
+
17
+ def to_params
18
+ params = {}
19
+ if !@json_facet.nil?
20
+ params['json.facet'] = recursive_add_stats(@json_facet.get_params).to_json
21
+ else
22
+ params.merge!(:stats => true, :"stats.field" => [@field.indexed_name])
23
+ params[facet_key] = @facets.map(&:indexed_name) unless @facets.empty?
24
+ end
25
+ params
26
+ end
27
+
28
+ STATS_FUNCTIONS = [:min, :max, :sum, :avg, :sumsq]
29
+
30
+ def recursive_add_stats(query)
31
+ query.keys.each do |k|
32
+ if !query[k][:facet].nil?
33
+ query[k][:facet] = recursive_add_stats(query[k][:facet])
34
+ end
35
+ query[k][:facet] ||= {}
36
+ query[k][:sort] = { @options[:sort] => @options[:sort_type]||'desc' } unless @options[:sort].nil?
37
+ query[k][:facet].merge!(json_stats_params)
38
+ end
39
+ query
40
+ end
41
+
42
+ def json_stats_params
43
+ params = {}
44
+ STATS_FUNCTIONS.each { |s| params[s] = "#{s.to_s}(#{@field.indexed_name})" }
45
+ unless @options[:stats].nil?
46
+ to_remove = STATS_FUNCTIONS - @options[:stats]
47
+ to_remove.map { |s| params.delete(s)}
48
+ end
49
+ params
50
+ end
51
+
52
+ def facet_key
53
+ qualified_param 'facet'
54
+ end
55
+
56
+ def qualified_param name
57
+ :"f.#{@field.indexed_name}.stats.#{name}"
58
+ end
59
+ end
60
+ end
61
+ end
@@ -4,7 +4,6 @@ module Sunspot
4
4
  # Abstract class for function queries.
5
5
  #
6
6
  class FunctionQuery
7
- include RSolr::Char
8
7
 
9
8
  def ^(y)
10
9
  @boost_amount = y
@@ -34,7 +33,7 @@ module Sunspot
34
33
  end
35
34
 
36
35
  def to_s
37
- "#{escape(@field.indexed_name)}" << (@boost_amount ? "^#{@boost_amount}" : "")
36
+ "#{Util.escape(@field.indexed_name)}" << (@boost_amount ? "^#{@boost_amount}" : "")
38
37
  end
39
38
  end
40
39
 
@@ -21,7 +21,7 @@ module Sunspot
21
21
  end
22
22
 
23
23
  def to_subquery
24
- "(#{to_boolean_query})"
24
+ { :q => "(#{to_boolean_query})" }
25
25
  end
26
26
 
27
27
  private
@@ -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
@@ -0,0 +1,46 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # A Group groups by the unique values of a given field, or by given queries.
5
+ #
6
+ class Group
7
+ attr_accessor :limit, :truncate, :ngroups
8
+ attr_reader :fields, :queries
9
+
10
+ def initialize
11
+ @sort = SortComposite.new
12
+ @fields = []
13
+ @queries = []
14
+ end
15
+
16
+ def add_field(field)
17
+ if field.multiple?
18
+ raise(ArgumentError, "#{field.name} cannot be used for grouping because it is a multiple-value field")
19
+ end
20
+ @fields << field
21
+ end
22
+
23
+ def add_query(query)
24
+ @queries << query
25
+ end
26
+
27
+ def add_sort(sort)
28
+ @sort << sort
29
+ end
30
+
31
+ def to_params
32
+ params = {
33
+ :group => 'true',
34
+ :"group.ngroups" => @ngroups.nil? ? 'true' : @ngroups.to_s
35
+ }
36
+
37
+ params.merge!(@sort.to_params('group.'))
38
+ params[:"group.field"] = @fields.map(&:indexed_name) if @fields.any?
39
+ params[:"group.query"] = @queries.map(&:to_boolean_phrase) if @queries.any?
40
+ params[:"group.limit"] = @limit if @limit
41
+ params[:"group.truncate"] = @truncate if @truncate
42
+ params
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,17 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # Representation of a GroupQuery, which allows the searcher to specify a
5
+ # query to group matching documents. This is essentially a conjunction,
6
+ # with an extra instance variable containing the label for the group.
7
+ #
8
+ class GroupQuery < Connective::Conjunction #:nodoc:
9
+ attr_reader :label
10
+
11
+ def initialize(label, negated = false)
12
+ super(negated)
13
+ @label = label
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,88 @@
1
+ module Sunspot
2
+ module Query
3
+
4
+ #
5
+ # Solr full-text queries use Solr's JoinRequestHandler.
6
+ #
7
+ class Join < AbstractFulltext
8
+ attr_writer :minimum_match, :phrase_slop, :query_phrase_slop, :tie
9
+
10
+ def initialize(keywords, target, from, to)
11
+ @keywords = keywords
12
+ @target = target
13
+ @from = from
14
+ @to = to
15
+
16
+ @fulltext_fields = {}
17
+
18
+ @minimum_match = nil
19
+ end
20
+
21
+ #
22
+ # The query as Solr parameters
23
+ #
24
+ def to_params
25
+ params = { :q => @keywords }
26
+ params[:fl] = '* score'
27
+ params[:qf] = @fulltext_fields.values.map { |field| field.to_boosted_field }.join(' ')
28
+ params[:defType] = 'join'
29
+ params[:mm] = @minimum_match if @minimum_match
30
+
31
+ params
32
+ end
33
+
34
+ #
35
+ # Serialize the query as a Solr nested subquery.
36
+ #
37
+ def to_subquery
38
+ params = self.to_params
39
+ params.delete :defType
40
+ params.delete :fl
41
+
42
+ keywords = escape_quotes(params.delete(:q))
43
+ options = params.map { |key, value| escape_param(key, value) }.join(' ')
44
+ q_name = "q#{@target.name}#{self.object_id}"
45
+
46
+ {
47
+ :q => "_query_:\"{!join from=#{@from} to=#{@to} v=$#{q_name}}\"",
48
+ q_name => "_query_:\"{!field f=type}#{@target.name}\"+_query_:\"{!edismax #{options}}#{keywords}\""
49
+ }
50
+ end
51
+
52
+ #
53
+ # Assign a new boost query and return it.
54
+ #
55
+ def create_boost_query(factor)
56
+ end
57
+
58
+ #
59
+ # Add a boost function
60
+ #
61
+ def add_boost_function(function_query)
62
+ end
63
+
64
+ #
65
+ # Add a fulltext field to be searched, with optional boost.
66
+ #
67
+ def add_fulltext_field(field, boost = nil)
68
+ super if field.is_a?(Sunspot::JoinField) &&
69
+ field.target == @target && field.from == @from && field.to == @to
70
+ end
71
+
72
+ #
73
+ # Add a phrase field for extra boost.
74
+ #
75
+ def add_phrase_field(field, boost = nil)
76
+ end
77
+
78
+ #
79
+ # Set highlighting options for the query. If fields is empty, the
80
+ # Highlighting object won't pass field names at all, which means
81
+ # the dismax's :qf parameter will be used by Solr.
82
+ #
83
+ def add_highlight(fields=[], options={})
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -50,7 +50,7 @@ module Sunspot
50
50
  params[:"mlt.fl"] = @fields.keys.join(",")
51
51
  boosted_fields = @fields.values.select { |field| field.boost }
52
52
  unless boosted_fields.empty?
53
- params[:qf] = boosted_fields.map do |field|
53
+ params[:"mlt.qf"] = boosted_fields.map do |field|
54
54
  field.to_boosted_field
55
55
  end.join(' ')
56
56
  end
@@ -6,14 +6,18 @@ module Sunspot
6
6
  # reference to it and updates it if pagination is changed.
7
7
  #
8
8
  class Pagination #:nodoc:
9
- attr_reader :page, :per_page, :offset
9
+ attr_reader :page, :per_page, :offset, :cursor
10
10
 
11
- def initialize(page = nil, per_page = nil, offset = nil)
12
- self.offset, self.page, self.per_page = offset, page, per_page
11
+ def initialize(page = nil, per_page = nil, offset = nil, cursor = nil)
12
+ self.offset, self.page, self.per_page, self.cursor = offset, page, per_page, cursor
13
13
  end
14
14
 
15
15
  def to_params
16
- { :start => start, :rows => rows }
16
+ if @cursor
17
+ { :cursorMark => @cursor, :rows => rows }
18
+ else
19
+ { :start => start, :rows => rows }
20
+ end
17
21
  end
18
22
 
19
23
  def page=(page)
@@ -28,6 +32,10 @@ module Sunspot
28
32
  @offset = offset.to_i
29
33
  end
30
34
 
35
+ def cursor=(cursor)
36
+ @cursor = cursor if cursor
37
+ end
38
+
31
39
  private
32
40
 
33
41
  def start
@@ -0,0 +1,28 @@
1
+ module Sunspot
2
+ module Query
3
+ class RangeJsonFacet < AbstractJsonFieldFacet
4
+
5
+ SECONDS_IN_DAY = 86400
6
+
7
+ def initialize(field, options, setup)
8
+ raise Exception.new("Need to specify a range") if options[:range].nil?
9
+ @start = options[:range].first
10
+ @end = options[:range].last
11
+ @gap = options[:gap] || SECONDS_IN_DAY
12
+ super
13
+ end
14
+
15
+ def field_name_with_local_params
16
+ {
17
+ @field.name => {
18
+ type: 'range',
19
+ field: @field.indexed_name,
20
+ start: @field.to_indexed(@start),
21
+ end: @field.to_indexed(@end),
22
+ gap: @gap
23
+ }.merge!(init_params)
24
+ }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -11,7 +11,7 @@ module Sunspot
11
11
  # Array:: Collection of restriction class names
12
12
  #
13
13
  def names
14
- constants - %w(Base) #XXX this seems ugly
14
+ constants - abstract_constants
15
15
  end
16
16
 
17
17
  #
@@ -22,6 +22,17 @@ module Sunspot
22
22
  @types ||= {}
23
23
  @types[restriction_name.to_sym] ||= const_get(Sunspot::Util.camel_case(restriction_name.to_s))
24
24
  end
25
+
26
+ private
27
+
28
+ #
29
+ # Return the names of all abstract restriction classes that should not
30
+ # be made available to the DSL. Considers abstract classes are any class
31
+ # ending with '::Base' or containing a namespace prefixed with 'Abstract'
32
+ #
33
+ def abstract_constants
34
+ constants.grep(/(^|::)(Base$|Abstract)/)
35
+ end
25
36
  end
26
37
 
27
38
  #
@@ -38,7 +49,6 @@ module Sunspot
38
49
  #
39
50
  class Base #:nodoc:
40
51
  include Filter
41
- include RSolr::Char
42
52
 
43
53
  RESERVED_WORDS = Set['AND', 'OR', 'NOT']
44
54
 
@@ -68,11 +78,14 @@ module Sunspot
68
78
  # on whether this restriction is negated.
69
79
  #
70
80
  def to_boolean_phrase
81
+ phrase = []
82
+ phrase << @field.local_params if @field.respond_to? :local_params
71
83
  unless negated?
72
- to_positive_boolean_phrase
84
+ phrase << to_positive_boolean_phrase
73
85
  else
74
- to_negated_boolean_phrase
86
+ phrase << to_negated_boolean_phrase
75
87
  end
88
+ phrase.join
76
89
  end
77
90
 
78
91
  #
@@ -89,7 +102,7 @@ module Sunspot
89
102
  # String:: Boolean phrase for restriction in the positive
90
103
  #
91
104
  def to_positive_boolean_phrase
92
- "#{escape(@field.indexed_name)}:#{to_solr_conditional}"
105
+ "#{Util.escape(@field.indexed_name)}:#{to_solr_conditional}"
93
106
  end
94
107
 
95
108
  #
@@ -122,7 +135,7 @@ module Sunspot
122
135
 
123
136
  protected
124
137
 
125
- #
138
+ #
126
139
  # Return escaped Solr API representation of given value
127
140
  #
128
141
  # ==== Parameters
@@ -135,7 +148,7 @@ module Sunspot
135
148
  # String:: Solr API representation of given value
136
149
  #
137
150
  def solr_value(value = @value)
138
- solr_value = escape(@field.to_indexed(value))
151
+ solr_value = Util.escape(@field.to_indexed(value))
139
152
  if RESERVED_WORDS.include?(solr_value)
140
153
  %Q("#{solr_value}")
141
154
  else
@@ -145,9 +158,13 @@ module Sunspot
145
158
  end
146
159
 
147
160
  class InRadius < Base
148
- def initialize(negated, field, lat, lon, radius)
149
- @lat, @lon, @radius = lat, lon, radius
150
- super negated, field, [lat, lon, radius]
161
+ def initialize(negated, field, *value)
162
+ @lat, @lon, @radius = value
163
+ super negated, field, value
164
+ end
165
+
166
+ def negate
167
+ self.class.new(!@negated, @field, *@value)
151
168
  end
152
169
 
153
170
  private
@@ -156,6 +173,17 @@ module Sunspot
156
173
  end
157
174
  end
158
175
 
176
+ class InBoundingBox < Base
177
+ def initialize(negated, field, first_corner, second_corner)
178
+ @bbox = Sunspot::Query::Bbox.new(field, first_corner, second_corner)
179
+ super negated, field, [first_corner, second_corner]
180
+ end
181
+
182
+ def to_solr_conditional
183
+ @bbox.to_solr_conditional
184
+ end
185
+ end
186
+
159
187
  #
160
188
  # Results must have field with value equal to given value. If the value
161
189
  # is nil, results must have no value for the given field.
@@ -165,7 +193,7 @@ module Sunspot
165
193
  unless @value.nil?
166
194
  super
167
195
  else
168
- "#{escape(@field.indexed_name)}:[* TO *]"
196
+ "#{Util.escape(@field.indexed_name)}:[* TO *]"
169
197
  end
170
198
  end
171
199
 
@@ -273,10 +301,23 @@ module Sunspot
273
301
  # Results must have field with value included in given collection
274
302
  #
275
303
  class AnyOf < Base
304
+
305
+ def negated?
306
+ if @value.empty?
307
+ false
308
+ else
309
+ super
310
+ end
311
+ end
312
+
276
313
  private
277
314
 
278
315
  def to_solr_conditional
279
- "(#{@value.map { |v| solr_value v } * ' OR '})"
316
+ if @value.empty?
317
+ "[* TO *]"
318
+ else
319
+ "(#{@value.map { |v| solr_value v } * ' OR '})"
320
+ end
280
321
  end
281
322
  end
282
323
 
@@ -285,10 +326,22 @@ module Sunspot
285
326
  # collection (only makes sense for fields with multiple values)
286
327
  #
287
328
  class AllOf < Base
329
+ def negated?
330
+ if @value.empty?
331
+ false
332
+ else
333
+ super
334
+ end
335
+ end
336
+
288
337
  private
289
338
 
290
339
  def to_solr_conditional
291
- "(#{@value.map { |v| solr_value v } * ' AND '})"
340
+ if @value.empty?
341
+ "[* TO *]"
342
+ else
343
+ "(#{@value.map { |v| solr_value v } * ' AND '})"
344
+ end
292
345
  end
293
346
  end
294
347
 
@@ -303,6 +356,39 @@ module Sunspot
303
356
  "#{solr_value(@value)}*"
304
357
  end
305
358
  end
359
+
360
+ class AbstractRange < Between
361
+ private
362
+
363
+ def operation
364
+ @operation || self.class.name.split('::').last
365
+ end
366
+
367
+ def solr_value(value = @value)
368
+ @field.to_indexed(value)
369
+ end
370
+
371
+ def to_positive_boolean_phrase
372
+ "_query_:\"{!field f=#{@field.indexed_name} op=#{operation}}#{solr_value}\""
373
+ end
374
+ end
375
+
376
+ class Containing < AbstractRange
377
+ def initialize(negated, field, value)
378
+ @operation = 'Contains'
379
+ super
380
+ end
381
+ end
382
+
383
+ class Intersecting < AbstractRange
384
+ def initialize(negated, field, value)
385
+ @operation = 'Intersects'
386
+ super
387
+ end
388
+ end
389
+
390
+ class Within < AbstractRange
391
+ end
306
392
  end
307
393
  end
308
394
  end