sunspot 2.2.7 → 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 (117) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rspec +2 -0
  4. data/Appraisals +7 -0
  5. data/Gemfile +0 -8
  6. data/lib/sunspot/adapters.rb +4 -1
  7. data/lib/sunspot/configuration.rb +1 -0
  8. data/lib/sunspot/data_extractor.rb +36 -6
  9. data/lib/sunspot/dsl/field_query.rb +11 -0
  10. data/lib/sunspot/dsl/field_stats.rb +7 -0
  11. data/lib/sunspot/dsl/fields.rb +16 -0
  12. data/lib/sunspot/dsl/group.rb +10 -0
  13. data/lib/sunspot/dsl/scope.rb +23 -18
  14. data/lib/sunspot/field.rb +11 -0
  15. data/lib/sunspot/field_factory.rb +6 -2
  16. data/lib/sunspot/query/abstract_json_field_facet.rb +70 -0
  17. data/lib/sunspot/query/bbox.rb +5 -1
  18. data/lib/sunspot/query/date_field_json_facet.rb +25 -0
  19. data/lib/sunspot/query/field_json_facet.rb +19 -0
  20. data/lib/sunspot/query/field_stats.rb +35 -2
  21. data/lib/sunspot/query/group.rb +4 -5
  22. data/lib/sunspot/query/join.rb +2 -4
  23. data/lib/sunspot/query/range_json_facet.rb +28 -0
  24. data/lib/sunspot/query/restriction.rb +19 -4
  25. data/lib/sunspot/query.rb +3 -3
  26. data/lib/sunspot/schema.rb +10 -2
  27. data/lib/sunspot/search/abstract_search.rb +14 -1
  28. data/lib/sunspot/search/field_json_facet.rb +33 -0
  29. data/lib/sunspot/search/hit.rb +6 -1
  30. data/lib/sunspot/search/hit_enumerable.rb +4 -1
  31. data/lib/sunspot/search/json_facet_row.rb +40 -0
  32. data/lib/sunspot/search/json_facet_stats.rb +23 -0
  33. data/lib/sunspot/search/standard_search.rb +2 -3
  34. data/lib/sunspot/search/stats_json_row.rb +82 -0
  35. data/lib/sunspot/search/stats_row.rb +3 -1
  36. data/lib/sunspot/search.rb +4 -3
  37. data/lib/sunspot/session.rb +13 -5
  38. data/lib/sunspot/setup.rb +31 -0
  39. data/lib/sunspot/util.rb +23 -0
  40. data/lib/sunspot/version.rb +1 -1
  41. data/spec/api/adapters_spec.rb +32 -19
  42. data/spec/api/batcher_spec.rb +15 -15
  43. data/spec/api/binding_spec.rb +3 -3
  44. data/spec/api/class_set_spec.rb +3 -3
  45. data/spec/api/data_extractor_spec.rb +39 -0
  46. data/spec/api/hit_enumerable_spec.rb +32 -9
  47. data/spec/api/indexer/attributes_spec.rb +31 -31
  48. data/spec/api/indexer/batch_spec.rb +8 -7
  49. data/spec/api/indexer/dynamic_fields_spec.rb +8 -8
  50. data/spec/api/indexer/fixed_fields_spec.rb +12 -12
  51. data/spec/api/indexer/fulltext_spec.rb +8 -8
  52. data/spec/api/indexer/removal_spec.rb +14 -14
  53. data/spec/api/indexer_spec.rb +2 -2
  54. data/spec/api/query/advanced_manipulation_examples.rb +3 -3
  55. data/spec/api/query/connectives_examples.rb +26 -14
  56. data/spec/api/query/dsl_spec.rb +17 -9
  57. data/spec/api/query/dynamic_fields_examples.rb +18 -18
  58. data/spec/api/query/faceting_examples.rb +62 -62
  59. data/spec/api/query/fulltext_examples.rb +63 -58
  60. data/spec/api/query/function_spec.rb +26 -26
  61. data/spec/api/query/geo_examples.rb +6 -6
  62. data/spec/api/query/group_spec.rb +6 -6
  63. data/spec/api/query/highlighting_examples.rb +26 -26
  64. data/spec/api/query/join_spec.rb +2 -2
  65. data/spec/api/query/more_like_this_spec.rb +29 -29
  66. data/spec/api/query/ordering_pagination_examples.rb +25 -25
  67. data/spec/api/query/scope_examples.rb +39 -39
  68. data/spec/api/query/spatial_examples.rb +3 -3
  69. data/spec/api/query/spellcheck_examples.rb +3 -3
  70. data/spec/api/query/standard_spec.rb +1 -1
  71. data/spec/api/query/stats_examples.rb +8 -8
  72. data/spec/api/query/text_field_scoping_examples.rb +5 -5
  73. data/spec/api/query/types_spec.rb +4 -4
  74. data/spec/api/search/cursor_paginated_collection_spec.rb +12 -12
  75. data/spec/api/search/dynamic_fields_spec.rb +4 -4
  76. data/spec/api/search/faceting_spec.rb +55 -52
  77. data/spec/api/search/highlighting_spec.rb +7 -7
  78. data/spec/api/search/hits_spec.rb +43 -29
  79. data/spec/api/search/paginated_collection_spec.rb +18 -18
  80. data/spec/api/search/results_spec.rb +13 -13
  81. data/spec/api/search/search_spec.rb +3 -3
  82. data/spec/api/search/stats_spec.rb +10 -10
  83. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +19 -18
  84. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +9 -9
  85. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +10 -6
  86. data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +10 -10
  87. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +14 -13
  88. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +2 -2
  89. data/spec/api/session_proxy/spec_helper.rb +1 -1
  90. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +9 -5
  91. data/spec/api/session_spec.rb +42 -42
  92. data/spec/api/sunspot_spec.rb +7 -4
  93. data/spec/helpers/integration_helper.rb +1 -0
  94. data/spec/integration/atomic_updates_spec.rb +25 -11
  95. data/spec/integration/dynamic_fields_spec.rb +10 -10
  96. data/spec/integration/faceting_spec.rb +252 -39
  97. data/spec/integration/field_grouping_spec.rb +35 -16
  98. data/spec/integration/field_lists_spec.rb +57 -0
  99. data/spec/integration/geospatial_spec.rb +34 -8
  100. data/spec/integration/highlighting_spec.rb +5 -5
  101. data/spec/integration/indexing_spec.rb +5 -5
  102. data/spec/integration/join_spec.rb +45 -0
  103. data/spec/integration/keyword_search_spec.rb +47 -45
  104. data/spec/integration/local_search_spec.rb +4 -4
  105. data/spec/integration/more_like_this_spec.rb +7 -7
  106. data/spec/integration/scoped_search_spec.rb +108 -108
  107. data/spec/integration/spellcheck_spec.rb +52 -7
  108. data/spec/integration/stats_spec.rb +54 -13
  109. data/spec/integration/stored_fields_spec.rb +1 -1
  110. data/spec/integration/test_pagination.rb +4 -4
  111. data/spec/integration/unicode_spec.rb +1 -1
  112. data/spec/mocks/adapters.rb +33 -0
  113. data/spec/mocks/photo.rb +14 -4
  114. data/spec/mocks/post.rb +9 -1
  115. data/spec/spec_helper.rb +11 -10
  116. data/sunspot.gemspec +3 -1
  117. metadata +49 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4bdef6ed9f0c2c63ec693c6db3fe71149987c50d
4
- data.tar.gz: c7451cff132715d22bff11d90bed4ba62bf516e2
2
+ SHA256:
3
+ metadata.gz: 179de5f5261bdb69d21038a0b2e36fbd2812e1fc71ef4c1efd77979a03e58179
4
+ data.tar.gz: 1f7f40d9737400cf61c23515c61ebde70140b1c1bf9df800d7e19ce27c3cbd0d
5
5
  SHA512:
6
- metadata.gz: 71b60a64a69d3397a7d81c01282f8ab80e8c7b662bdb4196e91c84b9c06787d8787a2f37ba80e163f653ac0635ecf6edf682b2fec27a0dbef8ecfb79e9fd62c0
7
- data.tar.gz: bdb4c1d76331b386db7a487b3161afbf4f10c7f5c3a74f0d45e1d1876d348f84ba7fa7dfc750d45a2342f0d73d8cf51b72567dae3972ffd5ac08549cd8bf5ec6
6
+ metadata.gz: ad59d5868f3461648d29c77b6368d7c5145ef6247ecca38878bf4f03bb3d2ab73fea2decf629936d58282b8067345198f6bff82be1347bb0cc92cb4537722d28
7
+ data.tar.gz: 3c4fd67940331fbdacffd665ca624119038bf12da2aa2b041adbd8260aef3921660b83803a912cf81384c7269f6b46d3b7fad1c883c34e1a1ba2ccbade2fd6dc
data/.gitignore CHANGED
@@ -11,3 +11,4 @@ pkg
11
11
  README.rdoc
12
12
  .bundle
13
13
  Gemfile.lock
14
+ gemfiles/*.gemfile*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Appraisals ADDED
@@ -0,0 +1,7 @@
1
+ appraise 'rsolr-1.x' do
2
+ gem 'rsolr', '>= 1', '< 2'
3
+ end
4
+
5
+ appraise 'rsolr-2.x' do
6
+ gem 'rsolr', '>= 2', '< 3'
7
+ end
data/Gemfile CHANGED
@@ -1,11 +1,3 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem 'sunspot_solr', :path => File.expand_path('../../sunspot_solr', __FILE__)
4
-
5
3
  gemspec
6
-
7
- group :development do
8
- gem 'rake'
9
- end
10
-
11
- gem 'rsolr', ENV['RSOLR'] if ENV['RSOLR']
@@ -63,7 +63,10 @@ module Sunspot
63
63
  # String:: ID for use in Solr
64
64
  #
65
65
  def index_id #:nodoc:
66
- InstanceAdapter.index_id_for(@instance.class.name, id)
66
+ setup = Sunspot::Setup.for(@instance.class)
67
+ id_prefix = setup ? setup.id_prefix_for(@instance) : nil
68
+
69
+ InstanceAdapter.index_id_for("#{id_prefix}#{@instance.class.name}", id)
67
70
  end
68
71
 
69
72
  class <<self
@@ -26,6 +26,7 @@ module Sunspot
26
26
  read_timeout nil
27
27
  open_timeout nil
28
28
  proxy nil
29
+ update_format :xml
29
30
  end
30
31
  master_solr do
31
32
  url nil
@@ -5,16 +5,46 @@ module Sunspot
5
5
  # method, which takes an object and returns the value extracted from it.
6
6
  #
7
7
  module DataExtractor #:nodoc: all
8
+ #
9
+ # Abstract extractor to perform common actions on extracted values
10
+ #
11
+ class AbstractExtractor
12
+ BLACKLIST_REGEXP = /[\x0-\x8\xB\xC\xE-\x1F\x7f]/
13
+
14
+ def value_for(object)
15
+ extract_value_from(object)
16
+ end
17
+
18
+ private
19
+
20
+ def extract_value_from(object)
21
+ case object
22
+ when String
23
+ remove_blacklisted_chars(object)
24
+ when Array
25
+ object.map { |o| extract_value_from(o) }
26
+ when Hash
27
+ object.inject({}) { |h, (k, v)| h.merge(extract_value_from(k) => extract_value_from(v)) }
28
+ else
29
+ object
30
+ end
31
+ end
32
+
33
+ def remove_blacklisted_chars(object)
34
+ object.gsub(BLACKLIST_REGEXP, '')
35
+ end
36
+ end
37
+
8
38
  #
9
39
  # AttributeExtractors extract data by simply calling a method on the block.
10
40
  #
11
- class AttributeExtractor
41
+ class AttributeExtractor < AbstractExtractor
12
42
  def initialize(attribute_name)
13
43
  @attribute_name = attribute_name
14
44
  end
15
45
 
16
46
  def value_for(object)
17
- object.send(@attribute_name)
47
+ super object.send(@attribute_name)
18
48
  end
19
49
  end
20
50
 
@@ -24,26 +54,26 @@ module Sunspot
24
54
  # as the argument to the block. Either way, the return value of the block is
25
55
  # the value returned by the extractor.
26
56
  #
27
- class BlockExtractor
57
+ class BlockExtractor < AbstractExtractor
28
58
  def initialize(&block)
29
59
  @block = block
30
60
  end
31
61
 
32
62
  def value_for(object)
33
- Util.instance_eval_or_call(object, &@block)
63
+ super Util.instance_eval_or_call(object, &@block)
34
64
  end
35
65
  end
36
66
 
37
67
  #
38
68
  # Constant data extractors simply return the same value for every object.
39
69
  #
40
- class Constant
70
+ class Constant < AbstractExtractor
41
71
  def initialize(value)
42
72
  @value = value
43
73
  end
44
74
 
45
75
  def value_for(object)
46
- @value
76
+ super @value
47
77
  end
48
78
  end
49
79
  end
@@ -333,6 +333,17 @@ module Sunspot
333
333
  end
334
334
  end
335
335
 
336
+ def json_facet(*field_names)
337
+ options = Sunspot::Util.extract_options_from(field_names)
338
+
339
+ field_names.each do |field_name|
340
+ field = @setup.field(field_name)
341
+ facet = Sunspot::Util.parse_json_facet(field_name, options, @setup)
342
+ @search.add_json_facet(field, options)
343
+ @query.add_query_facet(facet)
344
+ end
345
+ end
346
+
336
347
  def stats(*field_names, &block)
337
348
  options = Sunspot::Util.extract_options_from(field_names)
338
349
 
@@ -13,6 +13,13 @@ module Sunspot
13
13
  @search_stats.add_facet(field)
14
14
  end
15
15
  end
16
+
17
+ def json_facet(field_name, options = {})
18
+ field = @setup.field(field_name)
19
+ facet = Sunspot::Util.parse_json_facet(field_name, options, @setup)
20
+ @query_stats.add_json_facet(facet)
21
+ end
22
+
16
23
  end
17
24
  end
18
25
  end
@@ -55,6 +55,22 @@ module Sunspot
55
55
  @setup.add_document_boost(attr_name, &block)
56
56
  end
57
57
 
58
+ #
59
+ # If you use the compositeId router for shards, you can send documents
60
+ # with a prefix in the document ID which will be used to calculate the
61
+ # hash Solr uses to determine the shard a document is sent to for indexing.
62
+ # The prefix can be anything you’d like it to be (it doesn’t have to be
63
+ # the shard name, for example), but it must be consistent so Solr
64
+ # behaves consistently.
65
+ #
66
+ # ==== Parameters
67
+ #
68
+ # attr_name<Symbol,String>:: Attribute name to call or a string constant
69
+ #
70
+ def id_prefix(attr_name = nil, &block)
71
+ @setup.add_id_prefix(attr_name, &block)
72
+ end
73
+
58
74
  # method_missing is used to provide access to typed fields, because
59
75
  # developers should be able to add new Sunspot::Type implementations
60
76
  # dynamically and have them recognized inside the Fields DSL. Like #text,
@@ -59,6 +59,16 @@ module Sunspot
59
59
  @group.truncate = true
60
60
  end
61
61
 
62
+ #
63
+ # The group.ngroups option true return the total number of groups
64
+ # this is expensive and sometimes you don't need it!
65
+ # If ngroups is false paginated_collection last_page? and total_pages wont't work.
66
+ # Defaults to true.
67
+ #
68
+ def ngroups(enabled)
69
+ @group.ngroups = enabled
70
+ end
71
+
62
72
  # Specify the order that results should be returned in. This method can
63
73
  # be called multiple times; precedence will be in the order given.
64
74
  #
@@ -14,7 +14,12 @@ module Sunspot
14
14
 
15
15
  # Build a restriction to return only fields of the type in the results.
16
16
  def field_list(*args)
17
- @query.add_field_list(Sunspot::Query::FieldList.new(args.flatten))
17
+ list = args.flatten.map { |field| @setup.field(field.to_sym).indexed_name.to_sym }
18
+ @query.add_field_list(Sunspot::Query::FieldList.new([:id] + list)) unless list.empty?
19
+ end
20
+
21
+ def without_stored_fields
22
+ @query.add_field_list(Sunspot::Query::FieldList.new([:id]))
18
23
  end
19
24
 
20
25
  #
@@ -194,25 +199,25 @@ module Sunspot
194
199
 
195
200
  def add_restriction(negated, *args)
196
201
  case args.first
197
- when String, Symbol
198
- raise ArgumentError if args.length > 2
199
- field = @setup.field(args[0].to_sym)
200
- if args.length > 1
201
- value = args[1]
202
- @scope.add_shorthand_restriction(negated, field, value)
203
- else # NONE
204
- DSL::Restriction.new(field, @scope, negated)
205
- end
206
- else # args are instances
207
- @scope.add_restriction(
208
- negated,
209
- IdField.instance,
210
- Sunspot::Query::Restriction::AnyOf,
211
- args.flatten.map { |instance|
212
- Sunspot::Adapters::InstanceAdapter.adapt(instance).index_id }
213
- )
202
+ when String, Symbol
203
+ raise ArgumentError if args.length > 2
204
+ field = @setup.field(args[0].to_sym)
205
+ if args.length > 1
206
+ value = args[1]
207
+ @scope.add_shorthand_restriction(negated, field, value)
208
+ else # NONE
209
+ DSL::Restriction.new(field, @scope, negated)
214
210
  end
211
+ else # args are instances
212
+ @scope.add_restriction(
213
+ negated,
214
+ IdField.instance,
215
+ Sunspot::Query::Restriction::AnyOf,
216
+ args.flatten.map { |instance|
217
+ Sunspot::Adapters::InstanceAdapter.adapt(instance).index_id }
218
+ )
215
219
  end
220
+ end
216
221
  end
217
222
  end
218
223
  end
data/lib/sunspot/field.rb CHANGED
@@ -93,6 +93,17 @@ module Sunspot
93
93
  !!@joined
94
94
  end
95
95
 
96
+ #
97
+ # Whether the field is stored or not.
98
+ #
99
+ # ==== Returns
100
+ #
101
+ # Boolean:: True if this field is a stored field
102
+ #
103
+ def stored?
104
+ !!@stored
105
+ end
106
+
96
107
  def hash
97
108
  indexed_name.hash
98
109
  end
@@ -72,11 +72,15 @@ module Sunspot
72
72
  # into the Solr document for indexing.
73
73
  #
74
74
  def populate_document(document, model, options = {}) #:nodoc:
75
+ atomic_operation = options[:update] == :set
75
76
  value = extract_value(model, options)
76
- unless value.nil?
77
- Util.Array(@field.to_indexed(value)).each do |scalar_value|
77
+ if value != nil || atomic_operation
78
+ indexed_values = Util.Array(@field.to_indexed(value))
79
+ indexed_values = [nil] if indexed_values.empty? && atomic_operation
80
+ indexed_values.each do |scalar_value|
78
81
  field_options = {}
79
82
  field_options[:boost] = @field.boost if @field.boost
83
+ field_options[:null] = true if scalar_value.nil? && atomic_operation
80
84
  document.add_field(
81
85
  @field.indexed_name.to_sym,
82
86
  scalar_value,
@@ -0,0 +1,70 @@
1
+ module Sunspot
2
+ module Query
3
+ class AbstractJsonFieldFacet
4
+
5
+ attr_accessor :field
6
+
7
+ DISTINCT_STRATEGIES = [:unique, :hll]
8
+
9
+ def initialize(field, options, setup)
10
+ @field, @options, @setup = field, options, setup
11
+ end
12
+
13
+ def init_params
14
+ params = {}
15
+ params[:limit] = @options[:limit] unless @options[:limit].nil?
16
+ params[:mincount] = @options[:minimum_count] unless @options[:minimum_count].nil?
17
+ params[:sort] = { @options[:sort] => @options[:sort_type]||'desc' } unless @options[:sort].nil?
18
+ params[:prefix] = @options[:prefix] unless @options[:prefix].nil?
19
+ params[:offset] = @options[:offset] unless @options[:offset].nil?
20
+
21
+ if !@options[:distinct].nil?
22
+ dist_opts = @options[:distinct]
23
+ raise Exception.new("Need to specify a strategy") if dist_opts[:strategy].nil?
24
+ raise Exception.new("The strategy must be one of #{DISTINCT_STRATEGIES}") unless DISTINCT_STRATEGIES.include?(dist_opts[:strategy])
25
+ @stategy = dist_opts[:strategy]
26
+ @group_by = dist_opts[:group_by].nil? ? @field : @setup.field(dist_opts[:group_by])
27
+ params[:field] = @group_by.indexed_name
28
+ params[:facet] = {}
29
+ params[:facet][:distinct] = "#{@stategy}(#{@field.indexed_name})"
30
+ end
31
+
32
+ params
33
+ end
34
+
35
+ def get_params
36
+ query = field_name_with_local_params
37
+ nested_params = recursive_nested_params(@options)
38
+
39
+ if !nested_params.nil?
40
+ query[@field.name][:facet] ||= {}
41
+ query[@field.name][:facet].merge!(nested_params)
42
+ end
43
+ query
44
+ end
45
+
46
+ def to_params
47
+ { 'json.facet' => self.get_params.to_json }
48
+ end
49
+
50
+ private
51
+
52
+ def recursive_nested_params(options)
53
+ if !options[:nested].nil? && options[:nested].is_a?(Hash)
54
+ opts = options[:nested]
55
+ field_name = opts[:field]
56
+
57
+ options = Sunspot::Util.extract_options_from([opts])
58
+ params = Sunspot::Util.parse_json_facet(field_name, options, @setup).field_name_with_local_params
59
+ if !opts.nil?
60
+ nested_params = recursive_nested_params(opts)
61
+ params[field_name][:facet] = nested_params unless nested_params.nil?
62
+ end
63
+
64
+ params
65
+ end
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -5,8 +5,12 @@ module Sunspot
5
5
  @field, @first_corner, @second_corner = field, first_corner, second_corner
6
6
  end
7
7
 
8
+ def to_solr_conditional
9
+ "[#{@first_corner.join(",")} TO #{@second_corner.join(",")}]"
10
+ end
11
+
8
12
  def to_params
9
- filter = "#{@field.indexed_name}:[#{@first_corner.join(",")} TO #{@second_corner.join(",")}]"
13
+ filter = "#{@field.indexed_name}:#{to_solr_conditional}"
10
14
 
11
15
  {:fq => filter}
12
16
  end
@@ -0,0 +1,25 @@
1
+ module Sunspot
2
+ module Query
3
+ class DateFieldJsonFacet < AbstractJsonFieldFacet
4
+
5
+ def initialize(field, options, setup)
6
+ raise Exception.new('Need to specify a time_range') if options[:time_range].nil?
7
+ @start = options[:time_range].first
8
+ @end = options[:time_range].last
9
+ @gap = "+#{options[:gap] || 86400}SECONDS"
10
+ super
11
+ end
12
+
13
+ def field_name_with_local_params
14
+ params = {}
15
+ params[:type] = 'range'
16
+ params[:field] = @field.indexed_name
17
+ params[:start] = @field.to_indexed(@start)
18
+ params[:end] = @field.to_indexed(@end)
19
+ params[:gap] = @gap
20
+ params.merge!(init_params)
21
+ { @field.name => params }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -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
@@ -10,9 +10,42 @@ module Sunspot
10
10
  @facets << field
11
11
  end
12
12
 
13
+ def add_json_facet(json_facet)
14
+ @json_facet = json_facet
15
+ end
16
+
13
17
  def to_params
14
- params = { :stats => true, :"stats.field" => [@field.indexed_name]}
15
- params[facet_key] = @facets.map(&:indexed_name) unless @facets.empty?
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
16
49
  params
17
50
  end
18
51
 
@@ -4,7 +4,7 @@ module Sunspot
4
4
  # A Group groups by the unique values of a given field, or by given queries.
5
5
  #
6
6
  class Group
7
- attr_accessor :limit, :truncate
7
+ attr_accessor :limit, :truncate, :ngroups
8
8
  attr_reader :fields, :queries
9
9
 
10
10
  def initialize
@@ -30,16 +30,15 @@ module Sunspot
30
30
 
31
31
  def to_params
32
32
  params = {
33
- :group => "true",
34
- :"group.ngroups" => "true",
33
+ :group => 'true',
34
+ :"group.ngroups" => @ngroups.nil? ? 'true' : @ngroups.to_s
35
35
  }
36
36
 
37
- params.merge!(@sort.to_params("group."))
37
+ params.merge!(@sort.to_params('group.'))
38
38
  params[:"group.field"] = @fields.map(&:indexed_name) if @fields.any?
39
39
  params[:"group.query"] = @queries.map(&:to_boolean_phrase) if @queries.any?
40
40
  params[:"group.limit"] = @limit if @limit
41
41
  params[:"group.truncate"] = @truncate if @truncate
42
-
43
42
  params
44
43
  end
45
44
  end
@@ -42,12 +42,10 @@ module Sunspot
42
42
  keywords = escape_quotes(params.delete(:q))
43
43
  options = params.map { |key, value| escape_param(key, value) }.join(' ')
44
44
  q_name = "q#{@target.name}#{self.object_id}"
45
- fq_name = "f#{q_name}"
46
45
 
47
46
  {
48
- :q => "_query_:\"{!join from=#{@from} to=#{@to} v=$#{q_name} fq=$#{fq_name}}\"",
49
- q_name => "_query_:\"{!edismax #{options}}#{keywords}\"",
50
- fq_name => "type:#{@target.name}"
47
+ :q => "_query_:\"{!join from=#{@from} to=#{@to} v=$#{q_name}}\"",
48
+ q_name => "_query_:\"{!field f=type}#{@target.name}\"+_query_:\"{!edismax #{options}}#{keywords}\""
51
49
  }
52
50
  end
53
51
 
@@ -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
@@ -135,7 +135,7 @@ module Sunspot
135
135
 
136
136
  protected
137
137
 
138
- #
138
+ #
139
139
  # Return escaped Solr API representation of given value
140
140
  #
141
141
  # ==== Parameters
@@ -158,9 +158,13 @@ module Sunspot
158
158
  end
159
159
 
160
160
  class InRadius < Base
161
- def initialize(negated, field, lat, lon, radius)
162
- @lat, @lon, @radius = lat, lon, radius
163
- 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)
164
168
  end
165
169
 
166
170
  private
@@ -169,6 +173,17 @@ module Sunspot
169
173
  end
170
174
  end
171
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
+
172
187
  #
173
188
  # Results must have field with value equal to given value. If the value
174
189
  # is nil, results must have no value for the given field.
data/lib/sunspot/query.rb CHANGED
@@ -1,6 +1,6 @@
1
- %w(filter abstract_field_facet connective boost_query date_field_facet
2
- range_facet abstract_fulltext dismax join field_list
3
- field_facet highlighting pagination restriction common_query spellcheck
1
+ %w(filter abstract_field_facet abstract_json_field_facet connective boost_query date_field_facet field_json_facet
2
+ range_facet range_json_facet date_field_json_facet abstract_fulltext dismax join
3
+ field_list field_facet highlighting pagination restriction common_query spellcheck
4
4
  standard_query more_like_this more_like_this_query geo geofilt bbox query_facet
5
5
  scope sort sort_composite text_field_boost function_query field_stats
6
6
  composite_fulltext group group_query).each do |file|
@@ -88,8 +88,16 @@ module Sunspot
88
88
  # Return an XML representation of this schema using the ERB template
89
89
  #
90
90
  def to_xml
91
- template = File.join(File.dirname(__FILE__), '..', '..', 'templates', 'schema.xml.erb')
92
- ERB.new(File.read(template), nil, '-').result(binding)
91
+ template_path = File.join(File.dirname(__FILE__), '..', '..', 'templates', 'schema.xml.erb')
92
+ template_text = File.read(template_path)
93
+
94
+ erb = if RUBY_VERSION >= '2.6'
95
+ ERB.new(template_text, trim_mode: '-')
96
+ else
97
+ ERB.new(template_text, nil, '-')
98
+ end
99
+
100
+ erb.result(binding)
93
101
  end
94
102
 
95
103
  private