sunspot 2.2.7 → 2.5.0

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