sunspot 0.9.7

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 (101) hide show
  1. data/History.txt +83 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +154 -0
  4. data/Rakefile +9 -0
  5. data/TODO +9 -0
  6. data/VERSION.yml +4 -0
  7. data/bin/sunspot-configure-solr +46 -0
  8. data/bin/sunspot-solr +62 -0
  9. data/lib/light_config.rb +40 -0
  10. data/lib/sunspot.rb +469 -0
  11. data/lib/sunspot/adapters.rb +265 -0
  12. data/lib/sunspot/composite_setup.rb +186 -0
  13. data/lib/sunspot/configuration.rb +38 -0
  14. data/lib/sunspot/data_extractor.rb +47 -0
  15. data/lib/sunspot/dsl.rb +3 -0
  16. data/lib/sunspot/dsl/field_query.rb +72 -0
  17. data/lib/sunspot/dsl/fields.rb +86 -0
  18. data/lib/sunspot/dsl/query.rb +59 -0
  19. data/lib/sunspot/dsl/query_facet.rb +31 -0
  20. data/lib/sunspot/dsl/restriction.rb +25 -0
  21. data/lib/sunspot/dsl/scope.rb +193 -0
  22. data/lib/sunspot/dsl/search.rb +30 -0
  23. data/lib/sunspot/facet.rb +16 -0
  24. data/lib/sunspot/facet_data.rb +120 -0
  25. data/lib/sunspot/facet_row.rb +10 -0
  26. data/lib/sunspot/field.rb +157 -0
  27. data/lib/sunspot/field_factory.rb +126 -0
  28. data/lib/sunspot/indexer.rb +123 -0
  29. data/lib/sunspot/instantiated_facet.rb +42 -0
  30. data/lib/sunspot/instantiated_facet_row.rb +22 -0
  31. data/lib/sunspot/query.rb +191 -0
  32. data/lib/sunspot/query/base_query.rb +90 -0
  33. data/lib/sunspot/query/connective.rb +126 -0
  34. data/lib/sunspot/query/dynamic_query.rb +69 -0
  35. data/lib/sunspot/query/field_facet.rb +151 -0
  36. data/lib/sunspot/query/field_query.rb +63 -0
  37. data/lib/sunspot/query/pagination.rb +39 -0
  38. data/lib/sunspot/query/query_facet.rb +73 -0
  39. data/lib/sunspot/query/query_facet_row.rb +19 -0
  40. data/lib/sunspot/query/query_field_facet.rb +13 -0
  41. data/lib/sunspot/query/restriction.rb +233 -0
  42. data/lib/sunspot/query/scope.rb +165 -0
  43. data/lib/sunspot/query/sort.rb +36 -0
  44. data/lib/sunspot/query/sort_composite.rb +33 -0
  45. data/lib/sunspot/schema.rb +165 -0
  46. data/lib/sunspot/search.rb +219 -0
  47. data/lib/sunspot/search/hit.rb +66 -0
  48. data/lib/sunspot/session.rb +201 -0
  49. data/lib/sunspot/setup.rb +271 -0
  50. data/lib/sunspot/type.rb +200 -0
  51. data/lib/sunspot/util.rb +164 -0
  52. data/solr/etc/jetty.xml +212 -0
  53. data/solr/etc/webdefault.xml +379 -0
  54. data/solr/lib/jetty-6.1.3.jar +0 -0
  55. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  56. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  57. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  58. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  59. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  60. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  61. data/solr/solr/conf/elevate.xml +36 -0
  62. data/solr/solr/conf/protwords.txt +21 -0
  63. data/solr/solr/conf/schema.xml +50 -0
  64. data/solr/solr/conf/solrconfig.xml +696 -0
  65. data/solr/solr/conf/stopwords.txt +57 -0
  66. data/solr/solr/conf/synonyms.txt +31 -0
  67. data/solr/start.jar +0 -0
  68. data/solr/webapps/solr.war +0 -0
  69. data/spec/api/adapters_spec.rb +33 -0
  70. data/spec/api/build_search_spec.rb +1039 -0
  71. data/spec/api/indexer_spec.rb +311 -0
  72. data/spec/api/query_spec.rb +153 -0
  73. data/spec/api/search_retrieval_spec.rb +362 -0
  74. data/spec/api/session_spec.rb +157 -0
  75. data/spec/api/spec_helper.rb +1 -0
  76. data/spec/api/sunspot_spec.rb +18 -0
  77. data/spec/integration/dynamic_fields_spec.rb +55 -0
  78. data/spec/integration/faceting_spec.rb +169 -0
  79. data/spec/integration/keyword_search_spec.rb +83 -0
  80. data/spec/integration/scoped_search_spec.rb +289 -0
  81. data/spec/integration/spec_helper.rb +1 -0
  82. data/spec/integration/stored_fields_spec.rb +10 -0
  83. data/spec/integration/test_pagination.rb +32 -0
  84. data/spec/mocks/adapters.rb +32 -0
  85. data/spec/mocks/blog.rb +3 -0
  86. data/spec/mocks/comment.rb +19 -0
  87. data/spec/mocks/connection.rb +84 -0
  88. data/spec/mocks/mock_adapter.rb +30 -0
  89. data/spec/mocks/mock_record.rb +48 -0
  90. data/spec/mocks/photo.rb +8 -0
  91. data/spec/mocks/post.rb +73 -0
  92. data/spec/mocks/user.rb +8 -0
  93. data/spec/spec_helper.rb +47 -0
  94. data/tasks/gemspec.rake +25 -0
  95. data/tasks/rcov.rake +28 -0
  96. data/tasks/rdoc.rake +22 -0
  97. data/tasks/schema.rake +19 -0
  98. data/tasks/spec.rake +24 -0
  99. data/tasks/todo.rake +4 -0
  100. data/templates/schema.xml.haml +24 -0
  101. metadata +246 -0
@@ -0,0 +1,73 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # QueryFacets encapsulate requests for Sunspot's query faceting capability.
5
+ # They are created by the FieldQuery#add_query_facet method.
6
+ #
7
+ #--
8
+ #
9
+ # The actual concept of a QueryFacet is somewhat artificial - it provides a
10
+ # grouping for the facet at the Sunspot level, which provides a nicer and
11
+ # more consistent API in Sunspot; Solr does not provide any grouping for
12
+ # query facet rows, instead returning each requested row individually, keyed
13
+ # by the boolean phrase used in the facet query.
14
+ #
15
+ class QueryFacet
16
+ attr_reader :name #:nodoc:
17
+ attr_reader :field #:nodoc:
18
+
19
+ def initialize(name, setup = nil) #:nodoc:
20
+ @name = name
21
+ @setup = setup
22
+ @components = []
23
+ end
24
+
25
+ #
26
+ # Add a QueryFacetRow to this facet. The label argument becomes the value
27
+ # of the Sunspot::QueryFacetRow object corresponding to this query facet
28
+ # row.
29
+ #
30
+ # ==== Parameters
31
+ #
32
+ # label<Object>::
33
+ # An object that will become the value of the result row. Use whatever
34
+ # type is most intuitive.
35
+ #
36
+ # ==== Returns
37
+ #
38
+ # QueryFacetRow:: QueryFacetRow object containing scope for this row
39
+ #
40
+ def add_row(label)
41
+ @components << row = QueryFacetRow.new(label, @setup)
42
+ row
43
+ end
44
+
45
+ #
46
+ # Express this query facet as Solr parameters
47
+ #
48
+ # ==== Returns
49
+ #
50
+ # Hash:: Solr params hash
51
+ #
52
+ def to_params #:nodoc:
53
+ components = @components.map { |component| component.to_boolean_phrase }
54
+ components = components.first if components.length == 1
55
+ {
56
+ :facet => 'true',
57
+ :"facet.query" => components
58
+ }
59
+ end
60
+
61
+ #
62
+ # Get query facet rows (used when constructing results)
63
+ #
64
+ # ==== Returns
65
+ #
66
+ # Array:: Array of QueryFacetRow objects.
67
+ #
68
+ def rows #:nodoc:
69
+ @components
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,19 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # QueryFacetRow objects encapsulate restrictions for a particular
5
+ # QueryFacet. They also contain a label attribute, which is used as the
6
+ # value for the search result's corresponding facet row object.
7
+ #
8
+ # See Query::Scope for the API provided.
9
+ #
10
+ class QueryFacetRow < Connective::Conjunction
11
+ attr_reader :label #:nodoc:
12
+
13
+ def initialize(label, setup) #:nodoc:
14
+ super(setup)
15
+ @label = label
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Sunspot
2
+ module Query
3
+ class QueryFieldFacet < QueryFacet
4
+ def initialize(field, values)
5
+ super(field.name)
6
+ @field = field
7
+ values.each do |value|
8
+ add_row(value).add_component(Restriction::EqualTo.new(field, value))
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,233 @@
1
+ module Sunspot
2
+ module Query
3
+ module Restriction #:nodoc:
4
+ class <<self
5
+ #
6
+ # Return the names of all of the restriction classes that should be made
7
+ # available to the DSL.
8
+ #
9
+ # ==== Returns
10
+ #
11
+ # Array:: Collection of restriction class names
12
+ #
13
+ def names
14
+ constants - %w(Base SameAs) #XXX this seems ugly
15
+ end
16
+
17
+ def [](restriction_name)
18
+ @types ||= {}
19
+ @types[restriction_name.to_sym] ||= const_get(Sunspot::Util.camel_case(restriction_name.to_s))
20
+ end
21
+ end
22
+
23
+ #
24
+ # Subclasses of this class represent restrictions that can be applied to
25
+ # a Sunspot query. The Sunspot::DSL::Restriction class presents a builder
26
+ # API for instances of this class.
27
+ #
28
+ # Implementations of this class must respond to #to_params and
29
+ # #to_negated_params. Instead of implementing those methods, they may
30
+ # choose to implement any of:
31
+ #
32
+ # * #to_positive_boolean_phrase, and optionally #to_negated_boolean_phrase
33
+ # * #to_solr_conditional
34
+ #
35
+ class Base #:nodoc:
36
+ include RSolr::Char
37
+
38
+ def initialize(field, value, negated = false)
39
+ @field, @value, @negated = field, value, negated
40
+ end
41
+
42
+ #
43
+ # A hash representing this restriction in solr-ruby's parameter format.
44
+ # All restriction implementations must respond to this method; however,
45
+ # the base implementation delegates to the #to_positive_boolean_phrase method, so
46
+ # subclasses may (and probably should) choose to implement that method
47
+ # instead.
48
+ #
49
+ # ==== Returns
50
+ #
51
+ # Hash:: Representation of this restriction as solr-ruby parameters
52
+ #
53
+ def to_params
54
+ { :fq => [to_boolean_phrase] }
55
+ end
56
+
57
+ #
58
+ # Return the boolean phrase associated with this restriction object.
59
+ # Differentiates between positive and negated boolean phrases depending
60
+ # on whether this restriction is negated.
61
+ #
62
+ def to_boolean_phrase
63
+ unless negated?
64
+ to_positive_boolean_phrase
65
+ else
66
+ to_negated_boolean_phrase
67
+ end
68
+ end
69
+
70
+ #
71
+ # Boolean phrase representing this restriction in the positive. Subclasses
72
+ # may choose to implement this method rather than #to_params; however,
73
+ # this method delegates to the abstract #to_solr_conditional method, which
74
+ # in most cases will be what subclasses will want to implement.
75
+ # #to_solr_conditional contains the boolean phrase representing the
76
+ # condition but leaves out the field name (see built-in implementations
77
+ # for examples)
78
+ #
79
+ # ==== Returns
80
+ #
81
+ # String:: Boolean phrase for restriction in the positive
82
+ #
83
+ def to_positive_boolean_phrase
84
+ "#{escape(@field.indexed_name)}:#{to_solr_conditional}"
85
+ end
86
+
87
+ #
88
+ # Boolean phrase representing this restriction in the negated. Subclasses
89
+ # may choose to implement this method, but it is not necessary, as the
90
+ # base implementation delegates to #to_positive_boolean_phrase.
91
+ #
92
+ # ==== Returns
93
+ #
94
+ # String:: Boolean phrase for restriction in the negated
95
+ #
96
+ def to_negated_boolean_phrase
97
+ "-#{to_positive_boolean_phrase}"
98
+ end
99
+
100
+ #
101
+ # Whether this restriction should be negated from its original meaning
102
+ #
103
+ def negated? #:nodoc:
104
+ !!@negated
105
+ end
106
+
107
+ def negate
108
+ self.class.new(@field, @value, !@negated)
109
+ end
110
+
111
+ protected
112
+
113
+ #
114
+ # Return escaped Solr API representation of given value
115
+ #
116
+ # ==== Parameters
117
+ #
118
+ # value<Object>::
119
+ # value to convert to Solr representation (default: @value)
120
+ #
121
+ # ==== Returns
122
+ #
123
+ # String:: Solr API representation of given value
124
+ #
125
+ def solr_value(value = @value)
126
+ escape(@field.to_indexed(value))
127
+ end
128
+ end
129
+
130
+ #
131
+ # Results must have field with value equal to given value. If the value
132
+ # is nil, results must have no value for the given field.
133
+ #
134
+ class EqualTo < Base
135
+ def to_positive_boolean_phrase
136
+ unless @value.nil?
137
+ super
138
+ else
139
+ "#{escape(@field.indexed_name)}:[* TO *]"
140
+ end
141
+ end
142
+
143
+ def negated?
144
+ if @value.nil?
145
+ !super
146
+ else
147
+ super
148
+ end
149
+ end
150
+
151
+ private
152
+
153
+ def to_solr_conditional
154
+ "#{solr_value}"
155
+ end
156
+ end
157
+
158
+ #
159
+ # Results must have field with value less than given value
160
+ #
161
+ class LessThan < Base
162
+ private
163
+
164
+ def to_solr_conditional
165
+ "[* TO #{solr_value}]"
166
+ end
167
+ end
168
+
169
+ #
170
+ # Results must have field with value greater than given value
171
+ #
172
+ class GreaterThan < Base
173
+ private
174
+
175
+ def to_solr_conditional
176
+ "[#{solr_value} TO *]"
177
+ end
178
+ end
179
+
180
+ #
181
+ # Results must have field with value in given range
182
+ #
183
+ class Between < Base
184
+ private
185
+
186
+ def to_solr_conditional
187
+ "[#{solr_value(@value.first)} TO #{solr_value(@value.last)}]"
188
+ end
189
+ end
190
+
191
+ #
192
+ # Results must have field with value included in given collection
193
+ #
194
+ class AnyOf < Base
195
+ private
196
+
197
+ def to_solr_conditional
198
+ "(#{@value.map { |v| solr_value v } * ' OR '})"
199
+ end
200
+ end
201
+
202
+ #
203
+ # Results must have field with values matching all values in given
204
+ # collection (only makes sense for fields with multiple values)
205
+ #
206
+ class AllOf < Base
207
+ private
208
+
209
+ def to_solr_conditional
210
+ "(#{@value.map { |v| solr_value v } * ' AND '})"
211
+ end
212
+ end
213
+
214
+ #
215
+ # Result must be the exact instance given (only useful when negated).
216
+ #
217
+ class SameAs < Base
218
+ def initialize(object, negated = false)
219
+ @object, @negated = object, negated
220
+ end
221
+
222
+ def to_positive_boolean_phrase
223
+ adapter = Adapters::InstanceAdapter.adapt(@object)
224
+ "id:#{escape(adapter.index_id)}"
225
+ end
226
+
227
+ def negate
228
+ SameAs.new(@object, !negated?)
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,165 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # The Scope class encapsulates a set of restrictions that scope search
5
+ # results (as well as query facets rows). This class's API is exposed by
6
+ # Query::Query and Query::QueryFacetRow.
7
+ #
8
+ class Scope
9
+ #
10
+ # Add a restriction to the query.
11
+ #
12
+ # ==== Parameters
13
+ #
14
+ # field_name<Symbol>:: Name of the field to which the restriction applies
15
+ # restriction_type<Class,Symbol>::
16
+ # Subclass of Sunspot::Query::Restriction::Base, or snake_cased name as symbol
17
+ # (e.g., +:equal_to+)
18
+ # value<Object>::
19
+ # Value against which the restriction applies (e.g. less_than(2) has a
20
+ # value of 2)
21
+ # negated::
22
+ # Whether this restriction should be negated (use add_negated_restriction)
23
+ #
24
+ def add_restriction(field_name, restriction_type, value, negated = false)
25
+ if restriction_type.is_a?(Symbol)
26
+ restriction_type = Restriction[restriction_type]
27
+ end
28
+ add_component(
29
+ restriction = restriction_type.new(
30
+ build_field(field_name), value, negated
31
+ )
32
+ )
33
+ restriction
34
+ end
35
+
36
+ #
37
+ # Add a negated restriction to the query. The restriction will be taken as
38
+ # the opposite of its usual meaning (e.g., an :equal_to restriction will
39
+ # be "not equal to".
40
+ #
41
+ # ==== Parameters
42
+ #
43
+ # field_name<Symbol>:: Name of the field to which the restriction applies
44
+ # restriction_type<Class>::
45
+ # Subclass of Sunspot::Query::Restriction::Base to instantiate
46
+ # value<Object>::
47
+ # Value against which the restriction applies (e.g. less_than(2) has a
48
+ # value of 2)
49
+ #
50
+ def add_negated_restriction(field_name, restriction_type, value)
51
+ add_restriction(field_name, restriction_type, value, true)
52
+ end
53
+
54
+ #
55
+ # Add a disjunction to the scope. The disjunction can then take a set of
56
+ # restrictions, which are combined with OR semantics.
57
+ #
58
+ # ==== Returns
59
+ #
60
+ # Connective::Disjunction:: New disjunction
61
+ #
62
+ def add_disjunction
63
+ add_component(disjunction = Connective::Disjunction.new(setup))
64
+ disjunction
65
+ end
66
+
67
+ #
68
+ # Add a conjunction to the scope. In most cases, this will simply return
69
+ # the Scope object itself, since scopes by default combine their
70
+ # restrictions with OR semantics. The Connective::Disjunction class
71
+ # overrides this method to return a Connective::Conjunction.
72
+ #
73
+ # ==== Returns
74
+ #
75
+ # Scope:: Self or another scope with conjunctive semantics.
76
+ #
77
+ def add_conjunction
78
+ self
79
+ end
80
+
81
+ #
82
+ # Exclude a particular instance from the search results
83
+ #
84
+ # ==== Parameters
85
+ #
86
+ # instance<Object>:: instance to exclude from results
87
+ #
88
+ def exclude_instance(instance)
89
+ add_component(Restriction::SameAs.new(instance, true))
90
+ end
91
+
92
+ #
93
+ # Generate a DynamicQuery instance for the given base name.
94
+ # This gives you access to a subset of the Query API but the operations
95
+ # apply to dynamic fields inside the dynamic field definition specified
96
+ # by +base_name+.
97
+ #
98
+ # ==== Parameters
99
+ #
100
+ # base_name<Symbol>::
101
+ # Base name of the dynamic field definition to use in the dynamic query
102
+ # operations
103
+ #
104
+ # ==== Returns
105
+ #
106
+ # DynamicQuery::
107
+ # Instance providing dynamic query functionality for the given field
108
+ # definitions.
109
+ #
110
+ def dynamic_query(base_name)
111
+ DynamicQuery.new(setup.dynamic_field_factory(base_name), self)
112
+ end
113
+
114
+ #
115
+ # Determine which restriction type to add based on the type of the value.
116
+ # Used to interpret query conditions passed as a hash, as well as the
117
+ # short-form DSL::Scope#with method.
118
+ #
119
+ # ==== Parameters
120
+ #
121
+ # field_name<Symbol>:: Name of the field on which to apply the restriction
122
+ # value<Object,Array,Range>:: Value to which to apply to the restriction
123
+ #--
124
+ # negated<Boolean>:: Whether to negate the restriction.
125
+ #
126
+ def add_shorthand_restriction(field_name, value, negated = false) #:nodoc:
127
+ restriction_type =
128
+ case value
129
+ when Range
130
+ Restriction::Between
131
+ when Array
132
+ Restriction::AnyOf
133
+ else
134
+ Restriction::EqualTo
135
+ end
136
+ add_restriction(field_name, restriction_type, value, negated)
137
+ end
138
+
139
+ #
140
+ # Add a negated shorthand restriction. See #add_shorthand_restriction
141
+ #
142
+ def add_negated_shorthand_restriction(field_name, value)
143
+ add_shorthand_restriction(field_name, value, true)
144
+ end
145
+
146
+ private
147
+
148
+ #
149
+ # Build a field with the given field name. Subclasses may override this
150
+ # method.
151
+ #
152
+ def build_field(field_name)
153
+ setup.field(field_name)
154
+ end
155
+
156
+ #
157
+ # Return a setup object which can return a field object given a name.
158
+ # Subclasses may override this method.
159
+ #
160
+ def setup
161
+ @setup
162
+ end
163
+ end
164
+ end
165
+ end