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,126 @@
1
+ module Sunspot
2
+ module Query
3
+ module Connective #:nodoc:
4
+ #
5
+ # Base class for connectives (conjunctions and disjunctions).
6
+ #
7
+ class Abstract < Scope
8
+ def initialize(setup, negated = false) #:nodoc:
9
+ @setup, @negated = setup, negated
10
+ @components = []
11
+ end
12
+
13
+ #
14
+ # Connective as solr params.
15
+ #
16
+ def to_params #:nodoc:
17
+ { :fq => to_boolean_phrase }
18
+ end
19
+
20
+ #
21
+ # Express the connective as a Lucene boolean phrase.
22
+ #
23
+ def to_boolean_phrase #:nodoc:
24
+ phrase = if @components.length == 1
25
+ @components.first.to_boolean_phrase
26
+ else
27
+ component_phrases = @components.map do |component|
28
+ component.to_boolean_phrase
29
+ end
30
+ "(#{component_phrases.join(" #{connector} ")})"
31
+ end
32
+ if negated?
33
+ "-#{phrase}"
34
+ else
35
+ phrase
36
+ end
37
+ end
38
+
39
+ #
40
+ # Add a component to the connective. All components must implement the
41
+ # #to_boolean_phrase method.
42
+ #
43
+ def add_component(component) #:nodoc:
44
+ @components << component
45
+ end
46
+
47
+ def negated?
48
+ @negated
49
+ end
50
+
51
+ def negate
52
+ negated = self.class.new(@setup, !negated?)
53
+ for component in @components
54
+ negated.add_component(component)
55
+ end
56
+ negated
57
+ end
58
+ end
59
+
60
+ #
61
+ # Disjunctions combine their components with an OR operator.
62
+ #
63
+ class Disjunction < Abstract
64
+ class <<self
65
+ def inverse
66
+ Conjunction
67
+ end
68
+ end
69
+
70
+ def to_boolean_phrase
71
+ if @components.any? { |component| component.negated? }
72
+ denormalize.to_boolean_phrase
73
+ else
74
+ super
75
+ end
76
+ end
77
+
78
+ #
79
+ # Add a conjunction to the disjunction. This overrides the method in
80
+ # the Scope class since scopes are implicitly conjunctive and thus
81
+ # can return themselves as a conjunction. Inside a disjunction, however,
82
+ # a conjunction must explicitly be created.
83
+ #
84
+ def add_conjunction
85
+ @components << conjunction = Conjunction.new(setup)
86
+ conjunction
87
+ end
88
+
89
+ def add_disjunction
90
+ self
91
+ end
92
+
93
+ private
94
+
95
+ def connector
96
+ 'OR'
97
+ end
98
+
99
+ def denormalize
100
+ denormalized = self.class.inverse.new(@setup, !negated?)
101
+ for component in @components
102
+ denormalized.add_component(component.negate)
103
+ end
104
+ denormalized
105
+ end
106
+ end
107
+
108
+ #
109
+ # Conjunctions combine their components with an AND operator.
110
+ #
111
+ class Conjunction < Abstract
112
+ class <<self
113
+ def inverse
114
+ Disjunction
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def connector
121
+ 'AND'
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,69 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # A dynamic query is a proxy object that implements the API of the FieldQuery
5
+ # class, but wraps a dynamic field factory and thus applies the query
6
+ # components using dynamic field instances.
7
+ #--
8
+ # Dynamic queries do not hold their own state, but rather proxy to the query
9
+ # that generated them, adding components directly to the owning query's
10
+ # internal state.
11
+ #++
12
+ # DynamicQuery instances are publicly generated by the Query#dynamic_query
13
+ # factory method.
14
+ #
15
+ class DynamicQuery < FieldQuery
16
+ def initialize(dynamic_field_factory, query) #:nodoc:
17
+ @dynamic_field_factory, @query = dynamic_field_factory, query
18
+ end
19
+
20
+ #
21
+ # This has the same effect as calling Query#exclude_instance; it is
22
+ # included for interface completeness.
23
+ #
24
+ def exclude_instance(instance)
25
+ @query.exclude_instance(instance)
26
+ end
27
+
28
+ #
29
+ # This has the same effect as calling Query#exclude_instance; it is
30
+ # included for interface completeness.
31
+ #
32
+ def dynamic_query(field_name)
33
+ @query.dynamic_query(field_name)
34
+ end
35
+
36
+ #
37
+ # Add a Sort to the query
38
+ #
39
+ def add_sort(sort) #:nodoc:
40
+ @query.add_sort(sort)
41
+ end
42
+
43
+ #
44
+ # Add a component to the query
45
+ #
46
+ def add_component(component) #:nodoc:
47
+ @query.add_component(component)
48
+ end
49
+
50
+ private
51
+
52
+ #
53
+ # DynamicFieldFactory implements the part of the Setup interface that we
54
+ # need, so methods in DynamicQuery's superclasses can rely on it without
55
+ # knowing what it is.
56
+ #
57
+ def setup
58
+ @dynamic_field_factory
59
+ end
60
+
61
+ #
62
+ # So query facets can be added to the query from within dynamic queries
63
+ #
64
+ def query_facets
65
+ @query.query_facets
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,151 @@
1
+ require 'set'
2
+
3
+ module Sunspot
4
+ module Query
5
+ #
6
+ # Encapsulates a query component representing a field facet. Users create
7
+ # instances using DSL::Query#facet
8
+ #
9
+ class FieldFacet #:nodoc:
10
+ class <<self
11
+ protected :new
12
+
13
+ #
14
+ # Return the appropriate FieldFacet instance for the field and options.
15
+ # If a :time_range option is specified, and the field type is TimeType,
16
+ # build a DateFieldFacet. Otherwise, build a normal FieldFacet.
17
+ #
18
+ # ==== Returns
19
+ #
20
+ # FieldFacet:: FieldFacet instance of appropriate class.
21
+ #
22
+ def build(field, options)
23
+ if options.has_key?(:time_range)
24
+ unless field.type == Type::TimeType
25
+ raise(
26
+ ArgumentError,
27
+ ":time_range key can only be specified for time fields"
28
+ )
29
+ end
30
+ DateFieldFacet.new(field, options)
31
+ elsif options.has_key?(:only)
32
+ QueryFieldFacet.new(field, options.delete(:only))
33
+ else
34
+ FieldFacet.new(field, options)
35
+ end
36
+ end
37
+ end
38
+
39
+ def initialize(field, options)
40
+ @field, @options = field, options
41
+ end
42
+
43
+ # ==== Returns
44
+ #
45
+ # Hash:: solr-ruby params for this field facet
46
+ #
47
+ def to_params
48
+ params = { :"facet.field" => [@field.indexed_name], :facet => 'true' }
49
+ params[param_key(:sort)] =
50
+ case @options[:sort]
51
+ when :count then 'true'
52
+ when :index then 'false'
53
+ when nil
54
+ else raise(ArgumentError, 'Allowed facet sort options are :count and :index')
55
+ end
56
+ params[param_key(:limit)] = @options[:limit]
57
+ params[param_key(:mincount)] =
58
+ if @options[:minimum_count] then @options[:minimum_count]
59
+ elsif @options[:zeros] then 0
60
+ else 1
61
+ end
62
+ params
63
+ end
64
+
65
+ private
66
+
67
+ #
68
+ # Given a facet parameter name, return the appropriate Solr parameter for
69
+ # this facet.
70
+ #
71
+ # ==== Returns
72
+ #
73
+ # Symbol:: Solr query parameter key
74
+ #
75
+ def param_key(name)
76
+ :"f.#{@field.indexed_name}.facet.#{name}"
77
+ end
78
+ end
79
+
80
+ class DateFieldFacet < FieldFacet #:nodoc:
81
+ ALLOWED_OTHER = Set.new(%w(before after between none all))
82
+
83
+ #
84
+ # Convert the facet to date params.
85
+ #
86
+ def to_params
87
+ super.merge(
88
+ :"facet.date" => [@field.indexed_name],
89
+ param_key('date.start') => start_time.utc.xmlschema,
90
+ param_key('date.end') => end_time.utc.xmlschema,
91
+ param_key('date.gap') => "+#{interval}SECONDS",
92
+ param_key('date.other') => others
93
+ )
94
+ end
95
+
96
+ private
97
+
98
+ #
99
+ # Start time for facet range
100
+ #
101
+ # ==== Returns
102
+ #
103
+ # Time:: Start time
104
+ #
105
+ def start_time
106
+ @options[:time_range].first
107
+ end
108
+
109
+ #
110
+ # End time for facet range
111
+ #
112
+ # ==== Returns
113
+ #
114
+ # Time:: End time
115
+ #
116
+ def end_time
117
+ @options[:time_range].last
118
+ end
119
+
120
+ #
121
+ # Time interval that each facet row should cover. Default is 1 day.
122
+ #
123
+ # ===== Returns
124
+ #
125
+ # Integer:: Time interval in seconds
126
+ #
127
+ def interval
128
+ @options[:time_interval] || 86400
129
+ end
130
+
131
+ #
132
+ # Other time ranges to create facet rows for. Allowed values are defined
133
+ # in ALLOWED_OTHER constant.
134
+ #
135
+ def others
136
+ if others = @options[:time_other]
137
+ Array(others).map do |other|
138
+ other = other.to_s
139
+ unless ALLOWED_OTHER.include?(other)
140
+ raise(
141
+ ArgumentError,
142
+ "#{other.inspect} is not a valid argument for :time_other"
143
+ )
144
+ end
145
+ other
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,63 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # This class acts as a base class for query components that encapsulate
5
+ # operations on fields. It is subclassed by the Query::Query class and the
6
+ # Query::DynamicQuery class.
7
+ #
8
+ class FieldQuery < Scope
9
+ #
10
+ # Add a field facet. See Sunspot::Facet for more information.
11
+ #
12
+ # ==== Parameters
13
+ #
14
+ # field_name<Symbol>:: Name of the field on which to get a facet
15
+ #
16
+ # ==== Returns
17
+ #
18
+ # FieldFacet:: The field facet object
19
+ #
20
+ def add_field_facet(field_name, options = nil)
21
+ options ||= {}
22
+ facet =
23
+ if only = options.delete(:only)
24
+ query_facets[field_name.to_sym] = QueryFieldFacet.new(@setup.field(field_name), only)
25
+ else
26
+ FieldFacet.build(build_field(field_name), options)
27
+ end
28
+ add_component(facet)
29
+ end
30
+
31
+ #
32
+ # Add a query facet.
33
+ #
34
+ # ==== Parameters
35
+ #
36
+ # name<Symbol>::
37
+ # The name associated with the query facet. This is not passed to Solr,
38
+ # but allows the user to retrieve the facet result by passing the name
39
+ # to the Search#facet method.
40
+ #
41
+ # ==== Returns
42
+ #
43
+ # QueryFacet:: The query facet object
44
+ #
45
+ def add_query_facet(name)
46
+ add_component(facet = QueryFacet.new(name, setup))
47
+ query_facets[name.to_sym] = facet
48
+ end
49
+
50
+ #
51
+ # Set result ordering.
52
+ #
53
+ # ==== Parameters
54
+ #
55
+ # field_name<Symbol>:: Name of the field on which to order
56
+ # direction<Symbol>:: :asc or :desc (default :asc)
57
+ #
58
+ def order_by(field_name, direction = nil)
59
+ add_sort(Sort.new(build_field(field_name), direction))
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,39 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # A query component that holds information about pagination. Unlike other
5
+ # query components, this one is mutable, because the query itself holds a
6
+ # reference to it and updates it if pagination is changed.
7
+ #
8
+ class Pagination #:nodoc:
9
+ attr_reader :page, :per_page
10
+
11
+ def initialize(configuration, page = nil, per_page = nil)
12
+ @configuration = configuration
13
+ self.page, self.per_page = page, per_page
14
+ end
15
+
16
+ def to_params
17
+ { :start => start, :rows => rows }
18
+ end
19
+
20
+ def page=(page)
21
+ @page = page || 1
22
+ end
23
+
24
+ def per_page=(per_page)
25
+ @per_page = per_page || @configuration.pagination.default_per_page
26
+ end
27
+
28
+ private
29
+
30
+ def start
31
+ (@page - 1) * @per_page
32
+ end
33
+
34
+ def rows
35
+ @per_page
36
+ end
37
+ end
38
+ end
39
+ end