nxa-sunspot 0.10.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. data/History.txt +153 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +191 -0
  4. data/Rakefile +9 -0
  5. data/TODO +14 -0
  6. data/VERSION.yml +4 -0
  7. data/bin/sunspot-configure-solr +40 -0
  8. data/bin/sunspot-solr +95 -0
  9. data/lib/light_config.rb +40 -0
  10. data/lib/sunspot.rb +488 -0
  11. data/lib/sunspot/adapters.rb +265 -0
  12. data/lib/sunspot/composite_setup.rb +188 -0
  13. data/lib/sunspot/configuration.rb +56 -0
  14. data/lib/sunspot/data_extractor.rb +50 -0
  15. data/lib/sunspot/dsl.rb +4 -0
  16. data/lib/sunspot/dsl/field_query.rb +150 -0
  17. data/lib/sunspot/dsl/fields.rb +100 -0
  18. data/lib/sunspot/dsl/fulltext.rb +228 -0
  19. data/lib/sunspot/dsl/query.rb +162 -0
  20. data/lib/sunspot/dsl/query_facet.rb +36 -0
  21. data/lib/sunspot/dsl/restriction.rb +25 -0
  22. data/lib/sunspot/dsl/scope.rb +225 -0
  23. data/lib/sunspot/dsl/search.rb +30 -0
  24. data/lib/sunspot/field.rb +181 -0
  25. data/lib/sunspot/field_factory.rb +144 -0
  26. data/lib/sunspot/indexer.rb +133 -0
  27. data/lib/sunspot/query.rb +9 -0
  28. data/lib/sunspot/query/abstract_field_facet.rb +43 -0
  29. data/lib/sunspot/query/boost_query.rb +20 -0
  30. data/lib/sunspot/query/connective.rb +189 -0
  31. data/lib/sunspot/query/date_field_facet.rb +14 -0
  32. data/lib/sunspot/query/dismax.rb +88 -0
  33. data/lib/sunspot/query/field_facet.rb +9 -0
  34. data/lib/sunspot/query/highlighting.rb +55 -0
  35. data/lib/sunspot/query/local.rb +27 -0
  36. data/lib/sunspot/query/pagination.rb +38 -0
  37. data/lib/sunspot/query/query.rb +86 -0
  38. data/lib/sunspot/query/query_facet.rb +16 -0
  39. data/lib/sunspot/query/restriction.rb +254 -0
  40. data/lib/sunspot/query/scope.rb +9 -0
  41. data/lib/sunspot/query/sort.rb +105 -0
  42. data/lib/sunspot/query/sort_composite.rb +33 -0
  43. data/lib/sunspot/query/text_field_boost.rb +15 -0
  44. data/lib/sunspot/schema.rb +147 -0
  45. data/lib/sunspot/search.rb +216 -0
  46. data/lib/sunspot/search/date_facet.rb +35 -0
  47. data/lib/sunspot/search/facet_row.rb +27 -0
  48. data/lib/sunspot/search/field_facet.rb +44 -0
  49. data/lib/sunspot/search/highlight.rb +38 -0
  50. data/lib/sunspot/search/hit.rb +117 -0
  51. data/lib/sunspot/search/query_facet.rb +62 -0
  52. data/lib/sunspot/session.rb +236 -0
  53. data/lib/sunspot/setup.rb +323 -0
  54. data/lib/sunspot/text_field_setup.rb +29 -0
  55. data/lib/sunspot/type.rb +204 -0
  56. data/lib/sunspot/util.rb +210 -0
  57. data/solr/etc/jetty.xml +212 -0
  58. data/solr/etc/webdefault.xml +379 -0
  59. data/solr/lib/jetty-6.1.3.jar +0 -0
  60. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  61. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  62. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  63. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  64. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  65. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  66. data/solr/solr/conf/elevate.xml +36 -0
  67. data/solr/solr/conf/protwords.txt +21 -0
  68. data/solr/solr/conf/schema.xml +64 -0
  69. data/solr/solr/conf/solrconfig.xml +725 -0
  70. data/solr/solr/conf/stopwords.txt +57 -0
  71. data/solr/solr/conf/synonyms.txt +31 -0
  72. data/solr/solr/lib/geoapi-nogenerics-2.1-M2.jar +0 -0
  73. data/solr/solr/lib/gt2-referencing-2.3.1.jar +0 -0
  74. data/solr/solr/lib/jsr108-0.01.jar +0 -0
  75. data/solr/solr/lib/locallucene.jar +0 -0
  76. data/solr/solr/lib/localsolr.jar +0 -0
  77. data/solr/start.jar +0 -0
  78. data/solr/webapps/solr.war +0 -0
  79. data/spec/api/adapters_spec.rb +33 -0
  80. data/spec/api/indexer/attributes_spec.rb +110 -0
  81. data/spec/api/indexer/batch_spec.rb +46 -0
  82. data/spec/api/indexer/dynamic_fields_spec.rb +33 -0
  83. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  84. data/spec/api/indexer/fulltext_spec.rb +43 -0
  85. data/spec/api/indexer/removal_spec.rb +46 -0
  86. data/spec/api/indexer/spec_helper.rb +1 -0
  87. data/spec/api/indexer_spec.rb +4 -0
  88. data/spec/api/query/adjust_params_spec.rb +37 -0
  89. data/spec/api/query/connectives_spec.rb +176 -0
  90. data/spec/api/query/dsl_spec.rb +12 -0
  91. data/spec/api/query/dynamic_fields_spec.rb +149 -0
  92. data/spec/api/query/faceting_spec.rb +296 -0
  93. data/spec/api/query/fulltext_spec.rb +281 -0
  94. data/spec/api/query/highlighting_spec.rb +225 -0
  95. data/spec/api/query/local_spec.rb +62 -0
  96. data/spec/api/query/ordering_pagination_spec.rb +95 -0
  97. data/spec/api/query/scope_spec.rb +266 -0
  98. data/spec/api/query/spec_helper.rb +1 -0
  99. data/spec/api/query/text_field_scoping_spec.rb +30 -0
  100. data/spec/api/query/types_spec.rb +20 -0
  101. data/spec/api/search/dynamic_fields_spec.rb +27 -0
  102. data/spec/api/search/faceting_spec.rb +242 -0
  103. data/spec/api/search/highlighting_spec.rb +65 -0
  104. data/spec/api/search/hits_spec.rb +67 -0
  105. data/spec/api/search/results_spec.rb +52 -0
  106. data/spec/api/search/search_spec.rb +23 -0
  107. data/spec/api/search/spec_helper.rb +1 -0
  108. data/spec/api/session_spec.rb +198 -0
  109. data/spec/api/spec_helper.rb +1 -0
  110. data/spec/api/sunspot_spec.rb +18 -0
  111. data/spec/helpers/indexer_helper.rb +29 -0
  112. data/spec/helpers/query_helper.rb +13 -0
  113. data/spec/helpers/search_helper.rb +78 -0
  114. data/spec/integration/dynamic_fields_spec.rb +55 -0
  115. data/spec/integration/faceting_spec.rb +188 -0
  116. data/spec/integration/highlighting_spec.rb +22 -0
  117. data/spec/integration/indexing_spec.rb +7 -0
  118. data/spec/integration/keyword_search_spec.rb +245 -0
  119. data/spec/integration/local_search_spec.rb +56 -0
  120. data/spec/integration/scoped_search_spec.rb +303 -0
  121. data/spec/integration/spec_helper.rb +7 -0
  122. data/spec/integration/stored_fields_spec.rb +10 -0
  123. data/spec/integration/test_pagination.rb +32 -0
  124. data/spec/mocks/adapters.rb +32 -0
  125. data/spec/mocks/blog.rb +3 -0
  126. data/spec/mocks/comment.rb +19 -0
  127. data/spec/mocks/connection.rb +106 -0
  128. data/spec/mocks/mock_adapter.rb +30 -0
  129. data/spec/mocks/mock_record.rb +48 -0
  130. data/spec/mocks/photo.rb +11 -0
  131. data/spec/mocks/post.rb +75 -0
  132. data/spec/mocks/super_class.rb +2 -0
  133. data/spec/mocks/user.rb +8 -0
  134. data/spec/spec_helper.rb +68 -0
  135. data/tasks/gemspec.rake +42 -0
  136. data/tasks/rcov.rake +28 -0
  137. data/tasks/rdoc.rake +22 -0
  138. data/tasks/schema.rake +19 -0
  139. data/tasks/spec.rake +24 -0
  140. data/tasks/todo.rake +4 -0
  141. data/templates/schema.xml.erb +36 -0
  142. metadata +319 -0
@@ -0,0 +1,33 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # The SortComposite class encapsulates an ordered collection of Sort
5
+ # objects. It's necessary to keep this as a separate class as Solr takes
6
+ # the sort as a single parameter, so adding sorts as regular components
7
+ # would not merge correctly in the #to_params method.
8
+ #
9
+ class SortComposite #:nodoc:
10
+ def initialize
11
+ @sorts = []
12
+ end
13
+
14
+ #
15
+ # Add a sort to the composite
16
+ #
17
+ def <<(sort)
18
+ @sorts << sort
19
+ end
20
+
21
+ #
22
+ # Combine the sorts into a single param by joining them
23
+ #
24
+ def to_params
25
+ unless @sorts.empty?
26
+ { :sort => @sorts.map { |sort| sort.to_param } * ', ' }
27
+ else
28
+ {}
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ module Sunspot
2
+ module Query
3
+ class TextFieldBoost #:nodoc:
4
+ def initialize(field, boost = nil)
5
+ @field, @boost = field, boost
6
+ end
7
+
8
+ def to_boosted_field
9
+ boosted_field = @field.indexed_name
10
+ boosted_field.concat("^#{@boost}") if @boost
11
+ boosted_field
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,147 @@
1
+ require 'erb'
2
+
3
+ module Sunspot
4
+ #
5
+ # Object that encapsulates schema information for building a Solr schema.xml
6
+ # file. This class is used by the schema:compile task as well as the
7
+ # sunspot-configure-solr executable.
8
+ #
9
+ class Schema #:nodoc:all
10
+ FieldType = Struct.new(:name, :class_name, :suffix)
11
+ FieldVariant = Struct.new(:attribute, :suffix)
12
+
13
+ DEFAULT_TOKENIZER = 'solr.StandardTokenizerFactory'
14
+ DEFAULT_FILTERS = %w(solr.StandardFilterFactory solr.LowerCaseFilterFactory)
15
+
16
+ FIELD_TYPES = [
17
+ FieldType.new('boolean', 'Bool', 'b'),
18
+ FieldType.new('sfloat', 'SortableFloat', 'f'),
19
+ FieldType.new('date', 'Date', 'd'),
20
+ FieldType.new('sint', 'SortableInt', 'i'),
21
+ FieldType.new('string', 'Str', 's'),
22
+ FieldType.new('sdouble', 'SortableDouble', 'e'),
23
+ FieldType.new('slong', 'SortableLong', 'l')
24
+ ]
25
+
26
+ FIELD_VARIANTS = [
27
+ FieldVariant.new('multiValued', 'm'),
28
+ FieldVariant.new('stored', 's')
29
+ ]
30
+
31
+ attr_reader :tokenizer, :filters
32
+
33
+ def initialize
34
+ @tokenizer = DEFAULT_TOKENIZER
35
+ @filters = DEFAULT_FILTERS.dup
36
+ end
37
+
38
+ #
39
+ # Attribute field types defined in the schema
40
+ #
41
+ def types
42
+ FIELD_TYPES
43
+ end
44
+
45
+ #
46
+ # DynamicField instances representing all the available types and variants
47
+ #
48
+ def dynamic_fields
49
+ fields = []
50
+ variant_combinations.each do |field_variants|
51
+ FIELD_TYPES.each do |type|
52
+ fields << DynamicField.new(type, field_variants)
53
+ end
54
+ end
55
+ fields
56
+ end
57
+
58
+ #
59
+ # Which tokenizer to use for text fields
60
+ #
61
+ def tokenizer=(tokenizer)
62
+ @tokenizer =
63
+ if tokenizer =~ /\./
64
+ tokenizer
65
+ else
66
+ "solr.#{tokenizer}TokenizerFactory"
67
+ end
68
+ end
69
+
70
+ #
71
+ # Add a filter for text field tokenization
72
+ #
73
+ def add_filter(filter)
74
+ @filters <<
75
+ if filter =~ /\./
76
+ filter
77
+ else
78
+ "solr.#{filter}FilterFactory"
79
+ end
80
+ end
81
+
82
+ #
83
+ # Return an XML representation of this schema using the ERB template
84
+ #
85
+ def to_xml
86
+ template = File.join(File.dirname(__FILE__), '..', '..', 'templates', 'schema.xml.erb')
87
+ ERB.new(File.read(template), nil, '-').result(binding)
88
+ end
89
+
90
+ private
91
+
92
+ #
93
+ # All of the possible combinations of variants
94
+ #
95
+ def variant_combinations
96
+ combinations = []
97
+ 0.upto(2 ** FIELD_VARIANTS.length - 1) do |b|
98
+ combinations << combination = []
99
+ FIELD_VARIANTS.each_with_index do |variant, i|
100
+ combination << variant if b & 1<<i > 0
101
+ end
102
+ end
103
+ combinations
104
+ end
105
+
106
+ #
107
+ # Represents a dynamic field (in the Solr schema sense, not the Sunspot
108
+ # sense).
109
+ #
110
+ class DynamicField
111
+ def initialize(type, field_variants)
112
+ @type, @field_variants = type, field_variants
113
+ end
114
+
115
+ #
116
+ # Name of the field in the schema
117
+ #
118
+ def name
119
+ variant_suffixes = @field_variants.map { |variant| variant.suffix }.join
120
+ "*_#{@type.suffix}#{variant_suffixes}"
121
+ end
122
+
123
+ #
124
+ # Name of the type as defined in the schema
125
+ #
126
+ def type
127
+ @type.name
128
+ end
129
+
130
+ #
131
+ # Implement magic methods to ask if a field is of a particular variant.
132
+ # Returns "true" if the field is of that variant and "false" otherwise.
133
+ #
134
+ def method_missing(name, *args, &block)
135
+ if name.to_s =~ /\?$/ && args.empty?
136
+ if @field_variants.any? { |variant| "#{variant.attribute}?" == name.to_s }
137
+ 'true'
138
+ else
139
+ 'false'
140
+ end
141
+ else
142
+ super(name.to_sym, *args, &block)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,216 @@
1
+ %w(query_facet field_facet date_facet facet_row hit
2
+ highlight).each do |file|
3
+ require File.join(File.dirname(__FILE__), 'search', file)
4
+ end
5
+
6
+ module Sunspot
7
+ #
8
+ # This class encapsulates the results of a Solr search. It provides access
9
+ # to search results, total result count, facets, and pagination information.
10
+ # Instances of Search are returned by the Sunspot.search and
11
+ # Sunspot.new_search methods.
12
+ #
13
+ class Search
14
+ # Query information for this search. If you wish to build the query without
15
+ # using the search DSL, this method allows you to access the query API
16
+ # directly. See Sunspot#new_search for how to construct the search object
17
+ # in this case.
18
+ attr_reader :query
19
+
20
+ def initialize(connection, setup, query, configuration) #:nodoc:
21
+ @connection, @setup, @query = connection, setup, query
22
+ @query.paginate(1, configuration.pagination.default_per_page)
23
+ @facets = {}
24
+ end
25
+
26
+ #
27
+ # Execute the search on the Solr instance and store the results. If you
28
+ # use Sunspot#search() to construct your searches, there is no need to call
29
+ # this method as it has already been called. If you use
30
+ # Sunspot#new_search(), you will need to call this method after building the
31
+ # query.
32
+ #
33
+ def execute
34
+ reset
35
+ params = @query.to_params
36
+ @solr_result = @connection.select(params)
37
+ self
38
+ end
39
+ alias_method :execute!, :execute #:nodoc: deprecated
40
+
41
+ #
42
+ # Get the collection of results as instantiated objects. If WillPaginate is
43
+ # available, the results will be a WillPaginate::Collection instance; if
44
+ # not, it will be a vanilla Array.
45
+ #
46
+ # ==== Returns
47
+ #
48
+ # WillPaginate::Collection or Array:: Instantiated result objects
49
+ #
50
+ def results
51
+ @results ||= if @query.page && defined?(WillPaginate::Collection)
52
+ WillPaginate::Collection.create(@query.page, @query.per_page, total) do |pager|
53
+ pager.replace(hits.map { |hit| hit.instance })
54
+ end
55
+ else
56
+ hits.map { |hit| hit.instance }
57
+ end
58
+ end
59
+
60
+ #
61
+ # Access raw Solr result information. Returns a collection of Hit objects
62
+ # that contain the class name, primary key, keyword relevance score (if
63
+ # applicable), and any stored fields.
64
+ #
65
+ # ==== Returns
66
+ #
67
+ # Array:: Ordered collection of Hit objects
68
+ #
69
+ def hits
70
+ @hits ||= solr_response['docs'].map { |doc| Hit.new(doc, highlights_for(doc), self) }
71
+ end
72
+ alias_method :raw_results, :hits
73
+
74
+ #
75
+ # The total number of documents matching the query parameters
76
+ #
77
+ # ==== Returns
78
+ #
79
+ # Integer:: Total matching documents
80
+ #
81
+ def total
82
+ @total ||= solr_response['numFound']
83
+ end
84
+
85
+ #
86
+ # Get the facet object for the given name. `name` can either be the name
87
+ # given to a query facet, or the field name of a field facet. Returns a
88
+ # Sunspot::Facet object.
89
+ #
90
+ def facet(name)
91
+ @facets[name]
92
+ end
93
+
94
+ #
95
+ # Get the facet object for a given dynamic field. This dynamic field will
96
+ # need to have been requested as a field facet inside the search block.
97
+ #
98
+ # ==== Parameters
99
+ #
100
+ # base_name<Symbol>::
101
+ # Base name of the dynamic field definiton (as specified in the setup
102
+ # block)
103
+ # dynamic_name<Symbol>::
104
+ # Dynamic field name to facet on
105
+ #
106
+ # ==== Returns
107
+ #
108
+ # Sunspot::Facet:: Facet object for given dynamic field
109
+ #
110
+ # ==== Example
111
+ #
112
+ # search = Sunspot.search(Post) do
113
+ # dynamic :custom do
114
+ # facet :cuisine
115
+ # end
116
+ # end
117
+ # search.dynamic_facet(:custom, :cuisine)
118
+ # #=> Facet for the dynamic field :cuisine in the :custom field definition
119
+ #
120
+ def dynamic_facet(base_name, dynamic_name)
121
+ facet(:"#{base_name}:#{dynamic_name}")
122
+ end
123
+
124
+ #
125
+ # Get the data accessor that will be used to load a particular class out of
126
+ # persistent storage. Data accessors can implement any methods that may be
127
+ # useful for refining how data is loaded out of storage. When building a
128
+ # search manually (e.g., using the Sunspot#new_search method), this should
129
+ # be used before calling #execute(). Use the
130
+ # Sunspot::DSL::Search#data_accessor_for method when building searches using
131
+ # the block DSL.
132
+ #
133
+ def data_accessor_for(clazz) #:nodoc:
134
+ (@data_accessors ||= {})[clazz.name.to_sym] ||=
135
+ Adapters::DataAccessor.create(clazz)
136
+ end
137
+
138
+ #
139
+ # Build this search using a DSL block. This method can be called more than
140
+ # once on an unexecuted search (e.g., Sunspot.new_search) in order to build
141
+ # a search incrementally.
142
+ #
143
+ # === Example
144
+ #
145
+ # search = Sunspot.new_search(Post)
146
+ # search.build do
147
+ # with(:published_at).less_than Time.now
148
+ # end
149
+ # search.execute!
150
+ #
151
+ def build(&block)
152
+ Util.instance_eval_or_call(dsl, &block)
153
+ self
154
+ end
155
+
156
+ #
157
+ # Populate the Hit objects with their instances. This is invoked the first
158
+ # time any hit has its instance requested, and all hits are loaded as a
159
+ # batch.
160
+ #
161
+ def populate_hits! #:nodoc:
162
+ id_hit_hash = Hash.new { |h, k| h[k] = {} }
163
+ hits.each do |hit|
164
+ id_hit_hash[hit.class_name][hit.primary_key] = hit
165
+ end
166
+ id_hit_hash.each_pair do |class_name, hits|
167
+ ids = hits.map { |id, hit| hit.primary_key }
168
+ data_accessor_for(Util.full_const_get(class_name)).load_all(ids).each do |instance|
169
+ hit = id_hit_hash[class_name][Adapters::InstanceAdapter.adapt(instance).id.to_s]
170
+ hit.instance = instance
171
+ end
172
+ end
173
+ end
174
+
175
+ def inspect #:nodoc:
176
+ "<Sunspot::Search:#{query.to_params.inspect}>"
177
+ end
178
+
179
+ def add_field_facet(field, options = {}) #:nodoc:
180
+ @facets[field.name] = FieldFacet.new(field, self, options)
181
+ end
182
+
183
+ def add_date_facet(field, options) #:nodoc:
184
+ @facets[field.name] = DateFacet.new(field, self, options)
185
+ end
186
+
187
+ def add_query_facet(name, options) #:nodoc:
188
+ @facets[name] = QueryFacet.new(name, self, options)
189
+ end
190
+
191
+ def facet_response #:nodoc:
192
+ @solr_result['facet_counts']
193
+ end
194
+
195
+ private
196
+
197
+ def solr_response
198
+ @solr_response ||= @solr_result['response']
199
+ end
200
+
201
+ def dsl
202
+ DSL::Search.new(self, @setup)
203
+ end
204
+
205
+ def highlights_for(doc)
206
+ if @solr_result['highlighting']
207
+ @solr_result['highlighting'][doc['id']]
208
+ end
209
+ end
210
+
211
+ # Clear out all the cached ivars so the search can be called again.
212
+ def reset
213
+ @results = @hits = @total = @solr_response = @doc_ids = nil
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,35 @@
1
+ module Sunspot
2
+ class Search
3
+ class DateFacet
4
+ def initialize(field, search, options)
5
+ @field, @search, @options = field, search, options
6
+ end
7
+
8
+ def field_name
9
+ @field.name
10
+ end
11
+
12
+ def rows
13
+ @rows ||=
14
+ begin
15
+ data = @search.facet_response['facet_dates'][@field.indexed_name]
16
+ gap = (@options[:time_interval] || 86400).to_i
17
+ rows = []
18
+ data.each_pair do |value, count|
19
+ if value =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/
20
+ start_time = @field.cast(value)
21
+ end_time = start_time + gap
22
+ rows << FacetRow.new(start_time..end_time, count, self)
23
+ end
24
+ end
25
+ if @options[:sort] == :count
26
+ rows.sort! { |lrow, rrow| rrow.count <=> lrow.count }
27
+ else
28
+ rows.sort! { |lrow, rrow| lrow.value.first <=> rrow.value.first }
29
+ end
30
+ rows
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end