sunspot 0.10.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/History.txt +30 -0
  2. data/README.rdoc +59 -29
  3. data/Rakefile +2 -0
  4. data/TODO +9 -19
  5. data/VERSION.yml +2 -3
  6. data/bin/sunspot-installer +19 -0
  7. data/bin/sunspot-solr +28 -53
  8. data/installer/config/schema.yml +71 -0
  9. data/lib/sunspot/configuration.rb +0 -10
  10. data/lib/sunspot/dsl/field_query.rb +123 -7
  11. data/lib/sunspot/dsl/fields.rb +17 -4
  12. data/lib/sunspot/dsl/query.rb +11 -3
  13. data/lib/sunspot/dsl/scope.rb +6 -2
  14. data/lib/sunspot/field.rb +4 -7
  15. data/lib/sunspot/field_factory.rb +8 -5
  16. data/lib/sunspot/indexer.rb +22 -20
  17. data/lib/sunspot/installer/library_installer.rb +45 -0
  18. data/lib/sunspot/installer/schema_builder.rb +219 -0
  19. data/lib/sunspot/installer/solrconfig_updater.rb +90 -0
  20. data/lib/sunspot/installer/task_helper.rb +18 -0
  21. data/lib/sunspot/installer.rb +31 -0
  22. data/lib/sunspot/query/abstract_field_facet.rb +5 -1
  23. data/lib/sunspot/query/connective.rb +3 -1
  24. data/lib/sunspot/query/field_facet.rb +33 -1
  25. data/lib/sunspot/query/filter.rb +38 -0
  26. data/lib/sunspot/query/local.rb +10 -11
  27. data/lib/sunspot/query/query.rb +5 -12
  28. data/lib/sunspot/query/restriction.rb +2 -1
  29. data/lib/sunspot/query/scope.rb +1 -1
  30. data/lib/sunspot/query.rb +3 -3
  31. data/lib/sunspot/schema.rb +5 -1
  32. data/lib/sunspot/search/field_facet.rb +59 -15
  33. data/lib/sunspot/search/hit.rb +21 -10
  34. data/lib/sunspot/search/query_facet.rb +2 -2
  35. data/lib/sunspot/search.rb +86 -33
  36. data/lib/sunspot/server.rb +148 -0
  37. data/lib/sunspot/session.rb +39 -51
  38. data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
  39. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
  40. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
  41. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
  42. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +206 -0
  43. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +30 -0
  44. data/lib/sunspot/session_proxy.rb +71 -0
  45. data/lib/sunspot/setup.rb +24 -10
  46. data/lib/sunspot/type.rb +163 -101
  47. data/lib/sunspot/util.rb +44 -2
  48. data/lib/sunspot/version.rb +3 -0
  49. data/lib/sunspot.rb +50 -17
  50. data/solr/etc/jetty.xml +4 -2
  51. data/solr/solr/conf/admin-extra.html +31 -0
  52. data/solr/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
  53. data/solr/solr/conf/schema.xml +212 -50
  54. data/solr/solr/conf/scripts.conf +24 -0
  55. data/solr/solr/conf/solrconfig.xml +473 -266
  56. data/solr/solr/conf/spellings.txt +2 -0
  57. data/solr/solr/conf/stopwords.txt +1 -0
  58. data/solr/solr/lib/lucene-spatial-2.9.1.jar +0 -0
  59. data/solr/solr/lib/solr-spatial-light-0.0.3.jar +0 -0
  60. data/solr/webapps/solr.war +0 -0
  61. data/spec/api/binding_spec.rb +38 -0
  62. data/spec/api/indexer/attributes_spec.rb +37 -4
  63. data/spec/api/indexer/dynamic_fields_spec.rb +10 -1
  64. data/spec/api/indexer/removal_spec.rb +8 -1
  65. data/spec/api/indexer_spec.rb +10 -0
  66. data/spec/api/query/dsl_spec.rb +6 -0
  67. data/spec/api/query/dynamic_fields_spec.rb +9 -9
  68. data/spec/api/query/facet_local_params_spec.rb +103 -0
  69. data/spec/api/query/local_spec.rb +13 -37
  70. data/spec/api/query/ordering_pagination_spec.rb +0 -6
  71. data/spec/api/search/dynamic_fields_spec.rb +3 -3
  72. data/spec/api/search/faceting_spec.rb +113 -10
  73. data/spec/api/search/hits_spec.rb +49 -0
  74. data/spec/api/search/results_spec.rb +8 -1
  75. data/spec/api/server_spec.rb +85 -0
  76. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
  77. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
  78. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
  79. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
  80. data/spec/api/session_proxy/spec_helper.rb +9 -0
  81. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +33 -0
  82. data/spec/ext.rb +11 -0
  83. data/spec/helpers/search_helper.rb +6 -8
  84. data/spec/integration/faceting_spec.rb +46 -4
  85. data/spec/integration/indexing_spec.rb +27 -1
  86. data/spec/integration/local_search_spec.rb +25 -7
  87. data/spec/integration/scoped_search_spec.rb +48 -36
  88. data/spec/mocks/comment.rb +7 -5
  89. data/spec/mocks/connection.rb +14 -13
  90. data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
  91. data/spec/mocks/mock_record.rb +7 -3
  92. data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
  93. data/spec/mocks/photo.rb +4 -3
  94. data/spec/mocks/post.rb +1 -1
  95. data/spec/spec_helper.rb +2 -1
  96. data/tasks/gemspec.rake +26 -36
  97. data/tasks/rdoc.rake +9 -4
  98. metadata +203 -203
  99. data/bin/sunspot-configure-solr +0 -40
  100. data/solr/solr/lib/geoapi-nogenerics-2.1-M2.jar +0 -0
  101. data/solr/solr/lib/gt2-referencing-2.3.1.jar +0 -0
  102. data/solr/solr/lib/jsr108-0.01.jar +0 -0
  103. data/solr/solr/lib/locallucene.jar +0 -0
  104. data/solr/solr/lib/localsolr.jar +0 -0
  105. data/templates/schema.xml.erb +0 -36
@@ -0,0 +1,206 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_session_proxy')
2
+
3
+ module Sunspot
4
+ module SessionProxy
5
+ #
6
+ # This is a generic abstract implementation of a session proxy that allows
7
+ # Sunspot to be used with a distributed (sharded) Solr deployment. Concrete
8
+ # subclasses should implement the #session_for method, which takes a
9
+ # searchable object and returns a Session that points to the appropriate
10
+ # Solr shard for that object. Subclasses should also implement the
11
+ # #all_sessions object, which returns the collection of all sharded Session
12
+ # objects.
13
+ #
14
+ # The class is initialized with a session that points to the Solr instance
15
+ # used to perform searches. Searches will have the +:shards+ param injected,
16
+ # containing references to the Solr instances returned by #all_sessions.
17
+ #
18
+ # For more on distributed search, see:
19
+ # http://wiki.apache.org/solr/DistributedSearch
20
+ #
21
+ # The following methods are not supported (although subclasses may in some
22
+ # cases be able to support them):
23
+ #
24
+ # * batch
25
+ # * config
26
+ # * remove_by_id
27
+ # * remove_by_id!
28
+ # * remove_all with an argument
29
+ # * remove_all! with an argument
30
+ #
31
+ class ShardingSessionProxy < AbstractSessionProxy
32
+ not_supported :batch, :config, :remove_by_id, :remove_by_id!
33
+
34
+ #
35
+ # +search_session+ is the session that should be used for searching.
36
+ #
37
+ def initialize(search_session = Sunspot.session.new)
38
+ @search_session = search_session
39
+ end
40
+
41
+ #
42
+ # Return the appropriate shard session for the object.
43
+ #
44
+ # <strong>Concrete subclasses must implement this method.</strong>
45
+ #
46
+ def session_for(object)
47
+ raise NotImplementedError
48
+ end
49
+
50
+ #
51
+ # Return all shard sessions.
52
+ #
53
+ # <strong>Concrete subclasses must implement this method.</strong>
54
+ #
55
+ def all_sessions
56
+ raise NotImplementedError
57
+ end
58
+
59
+ #
60
+ # See Sunspot.index
61
+ #
62
+ def index(*objects)
63
+ using_sharded_session(objects) { |session, group| session.index(group) }
64
+ end
65
+
66
+ #
67
+ # See Sunspot.index!
68
+ #
69
+ def index!(*objects)
70
+ using_sharded_session(objects) { |session, group| session.index!(group) }
71
+ end
72
+
73
+ #
74
+ # See Sunspot.remove
75
+ #
76
+ def remove(*objects)
77
+ using_sharded_session(objects) { |session, group| session.remove(group) }
78
+ end
79
+
80
+ #
81
+ # See Sunspot.remove!
82
+ #
83
+ def remove!(*objects)
84
+ using_sharded_session(objects) { |session, group| session.remove!(group) }
85
+ end
86
+
87
+ #
88
+ # If no argument is passed, behaves like Sunspot.remove_all
89
+ #
90
+ # If an argument is passed, will raise NotSupportedError, as the proxy
91
+ # does not know which session(s) to which to delegate this operation.
92
+ #
93
+ def remove_all(clazz = nil)
94
+ if clazz
95
+ raise NotSupportedError, "Sharding session proxy does not support remove_all with an argument."
96
+ else
97
+ all_sessions.each { |session| session.remove_all }
98
+ end
99
+ end
100
+
101
+ #
102
+ # If no argument is passed, behaves like Sunspot.remove_all!
103
+ #
104
+ # If an argument is passed, will raise NotSupportedError, as the proxy
105
+ # does not know which session(s) to which to delegate this operation.
106
+ #
107
+ def remove_all!(clazz = nil)
108
+ if clazz
109
+ raise NotSupportedError, "Sharding session proxy does not support remove_all! with an argument."
110
+ else
111
+ all_sessions.each { |session| session.remove_all! }
112
+ end
113
+ end
114
+
115
+ #
116
+ # Commit all shards. See Sunspot.commit
117
+ #
118
+ def commit
119
+ all_sessions.each { |session| session.commit }
120
+ end
121
+
122
+ #
123
+ # Commit all dirty sessions. Only dirty sessions will be committed.
124
+ #
125
+ # See Sunspot.commit_if_dirty
126
+ #
127
+ def commit_if_dirty
128
+ all_sessions.each { |session| session.commit_if_dirty }
129
+ end
130
+
131
+ #
132
+ # Commit all delete-dirty sessions. Only delete-dirty sessions will be
133
+ # committed.
134
+ #
135
+ # See Sunspot.commit_if_delete_dirty
136
+ #
137
+ def commit_if_delete_dirty
138
+ all_sessions.each { |session| session.commit_if_delete_dirty }
139
+ end
140
+
141
+ #
142
+ # Instantiate a new Search object, but don't execute it. The search will
143
+ # have an extra :shards param injected into the query, which will tell the
144
+ # Solr instance referenced by the search session to search across all
145
+ # shards.
146
+ #
147
+ # See Sunspot.new_search
148
+ #
149
+ def new_search(*types)
150
+ shard_urls = all_sessions.map { |session| session.config.solr.url }
151
+ search = @search_session.new_search(*types)
152
+ search.build do
153
+ adjust_solr_params { |params| params[:shards] = shard_urls.join(',') }
154
+ # I feel a little dirty doing this.
155
+ end
156
+ search
157
+ end
158
+
159
+ #
160
+ # Build and execute a new Search. The search will have an extra :shards
161
+ # param injected into the query, which will tell the Solr instance
162
+ # referenced by the search session to search across all shards.
163
+ #
164
+ # See Sunspot.search
165
+ #
166
+ def search(*types, &block)
167
+ new_search(*types).execute
168
+ end
169
+
170
+ #
171
+ # True if any shard session is dirty. Note that directly using the
172
+ # #commit_if_dirty method is more efficient if that's what you're
173
+ # trying to do, since in that case only the dirty sessions are committed.
174
+ #
175
+ # See Sunspot.dirty?
176
+ #
177
+ def dirty?
178
+ all_sessions.any? { |session| session.dirty? }
179
+ end
180
+
181
+ #
182
+ # True if any shard session is delete-dirty. Note that directly using the
183
+ # #commit_if_delete_dirty method is more efficient if that's what you're
184
+ # trying to do, since in that case only the delete-dirty sessions are
185
+ # committed.
186
+ #
187
+ def delete_dirty?
188
+ all_sessions.any? { |session| session.delete_dirty? }
189
+ end
190
+
191
+ private
192
+
193
+ #
194
+ # Group the objects by which shard session they correspond to, and yield
195
+ # each session and is corresponding group of objects.
196
+ #
197
+ def using_sharded_session(objects)
198
+ grouped_objects = Hash.new { |h, k| h[k] = [] }
199
+ objects.flatten.each { |object| grouped_objects[session_for(object)] << object }
200
+ grouped_objects.each_pair do |session, group|
201
+ yield(session, group)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,30 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_session_proxy')
2
+
3
+ module Sunspot
4
+ module SessionProxy
5
+ #
6
+ # This class implements a session proxy that creates a different Session
7
+ # object for each thread. Any multithreaded application should use this
8
+ # proxy.
9
+ #
10
+ class ThreadLocalSessionProxy < AbstractSessionProxy
11
+ # The configuration with which the thread-local sessions are initialized.
12
+ attr_reader :config
13
+
14
+ delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty, :delete_dirty?, :dirty?, :index, :index!, :new_search, :remove, :remove!, :remove_all, :remove_all!, :remove_by_id, :remove_by_id!, :search, :to => :session
15
+
16
+ #
17
+ # Optionally pass an existing Sunspot::Configuration object. If none is
18
+ # passed, a default configuration is used; it can then be modified using
19
+ # the #config attribute.
20
+ #
21
+ def initialize(config = Sunspot::Configuration.new)
22
+ @config = config
23
+ end
24
+
25
+ def session #:nodoc:
26
+ Thread.current[:sunspot_session] ||= Session.new(config)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,71 @@
1
+ module Sunspot
2
+ #
3
+ # This module contains several Session Proxy implementations, which can be
4
+ # used to decorate one or more Session objects and add extra functionality.
5
+ # The user can also implement their own Session Proxy classes; a Session Proxy
6
+ # must simply implement the same public API as the Sunspot::Session class.
7
+ #
8
+ # When implementing a session proxy, some methods of Session may not be
9
+ # practical, or even logical, to implement. In this case, the method should
10
+ # raise a Sunspot::SessionProxy::NotSupportedError (several methods in the
11
+ # built-in session proxies raise this error).
12
+ #
13
+ # To use a session proxy in normal Sunspot usage, you can use the
14
+ # Sunspot.session= method, which will cause Sunspot to delegate all of its
15
+ # session-related class methods (most of them) to the proxy. Session proxies
16
+ # can also easily be chained, although the details of chaining depend on the
17
+ # proxy implementation.
18
+ #
19
+ # ===== Example: Chain a MasterSlaveSessionProxy with a ThreadLocalSessionProxy
20
+ #
21
+ # master_session = Sunspot::SessionProxy::ThreadLocalSessionProxy.new
22
+ # slave_session = Sunspot::SessionProxy::ThreadLocalSessionProxy.new
23
+ # master_session.config.solr.url = 'http://master-solr.local:9080/solr'
24
+ # slave_session.config.solr.url = 'http://slave-solr.local:9080/solr'
25
+ # Sunspot.session = Sunspot::SessionProxy::MasterSlaveSessionProxy.new(master_session, slave_session)
26
+ #
27
+ module SessionProxy
28
+ NotSupportedError = Class.new(StandardError)
29
+
30
+ autoload(
31
+ :ThreadLocalSessionProxy,
32
+ File.join(
33
+ File.dirname(__FILE__),
34
+ 'session_proxy',
35
+ 'thread_local_session_proxy'
36
+ )
37
+ )
38
+ autoload(
39
+ :MasterSlaveSessionProxy,
40
+ File.join(
41
+ File.dirname(__FILE__),
42
+ 'session_proxy',
43
+ 'master_slave_session_proxy'
44
+ )
45
+ )
46
+ autoload(
47
+ :ShardingSessionProxy,
48
+ File.join(
49
+ File.dirname(__FILE__),
50
+ 'session_proxy',
51
+ 'sharding_session_proxy'
52
+ )
53
+ )
54
+ autoload(
55
+ :ClassShardingSessionProxy,
56
+ File.join(
57
+ File.dirname(__FILE__),
58
+ 'session_proxy',
59
+ 'class_sharding_session_proxy'
60
+ )
61
+ )
62
+ autoload(
63
+ :IdShardingSessionProxy,
64
+ File.join(
65
+ File.dirname(__FILE__),
66
+ 'session_proxy',
67
+ 'id_sharding_session_proxy'
68
+ )
69
+ )
70
+ end
71
+ end
data/lib/sunspot/setup.rb CHANGED
@@ -4,15 +4,16 @@ module Sunspot
4
4
  # contents are built using the Sunspot.setup method.
5
5
  #
6
6
  class Setup #:nodoc:
7
+ attr_reader :class_object_id
7
8
  def initialize(clazz)
8
- @clazz = clazz
9
+ @class_object_id = clazz.object_id
9
10
  @class_name = clazz.name
10
11
  @field_factories, @text_field_factories, @dynamic_field_factories,
11
12
  @field_factories_cache, @text_field_factories_cache,
12
13
  @dynamic_field_factories_cache = *Array.new(6) { Hash.new }
13
14
  @stored_field_factories_cache = Hash.new { |h, k| h[k] = [] }
14
15
  @dsl = DSL::Fields.new(self)
15
- add_field_factory(:class, Type::ClassType)
16
+ add_field_factory(:class, Type::ClassType.instance)
16
17
  end
17
18
 
18
19
  def type_names
@@ -41,7 +42,7 @@ module Sunspot
41
42
  #
42
43
  def add_text_field_factory(name, options = {}, &block)
43
44
  stored = options[:stored]
44
- field_factory = FieldFactory::Static.new(name, Type::TextType, options, &block)
45
+ field_factory = FieldFactory::Static.new(name, Type::TextType.instance, options, &block)
45
46
  @text_field_factories[name] = field_factory
46
47
  @text_field_factories_cache[field_factory.name] = field_factory
47
48
  if stored
@@ -57,9 +58,13 @@ module Sunspot
57
58
  # field_factories<Array>:: Array of dynamic field objects
58
59
  #
59
60
  def add_dynamic_field_factory(name, type, options = {}, &block)
61
+ stored = options[:stored]
60
62
  field_factory = FieldFactory::Dynamic.new(name, type, options, &block)
61
63
  @dynamic_field_factories[field_factory.signature] = field_factory
62
64
  @dynamic_field_factories_cache[field_factory.name] = field_factory
65
+ if stored
66
+ @stored_field_factories_cache[field_factory.name] << field_factory
67
+ end
63
68
  end
64
69
 
65
70
  #
@@ -93,7 +98,7 @@ module Sunspot
93
98
  # Builder method for evaluating the setup DSL
94
99
  #
95
100
  def setup(&block)
96
- @dsl.instance_eval(&block)
101
+ Util.instance_eval_or_call(@dsl, &block)
97
102
  end
98
103
 
99
104
  #
@@ -105,7 +110,7 @@ module Sunspot
105
110
  else
106
111
  raise(
107
112
  UnrecognizedFieldError,
108
- "No field configured for #{@clazz.name} with name '#{field_name}'"
113
+ "No field configured for #{@class_name} with name '#{field_name}'"
109
114
  )
110
115
  end
111
116
  end
@@ -122,7 +127,7 @@ module Sunspot
122
127
  else
123
128
  raise(
124
129
  UnrecognizedFieldError,
125
- "No text field configured for #{@clazz.name} with name '#{field_name}'"
130
+ "No text field configured for #{@class_name} with name '#{field_name}'"
126
131
  )
127
132
  end
128
133
  [text_field]
@@ -132,9 +137,13 @@ module Sunspot
132
137
  # Return one or more stored fields (can be either attribute or text fields)
133
138
  # for the given name.
134
139
  #
135
- def stored_fields(field_name)
140
+ def stored_fields(field_name, dynamic_field_name = nil)
136
141
  @stored_field_factories_cache[field_name.to_sym].map do |field_factory|
137
- field_factory.build
142
+ if dynamic_field_name
143
+ field_factory.build(dynamic_field_name)
144
+ else
145
+ field_factory.build
146
+ end
138
147
  end
139
148
  end
140
149
 
@@ -144,7 +153,7 @@ module Sunspot
144
153
  def dynamic_field_factory(field_name)
145
154
  @dynamic_field_factories_cache[field_name.to_sym] || raise(
146
155
  UnrecognizedFieldError,
147
- "No dynamic field configured for #{@clazz.name} with name '#{field_name}'"
156
+ "No dynamic field configured for #{@class_name} with name '#{field_name}'"
148
157
  )
149
158
  end
150
159
 
@@ -304,7 +313,12 @@ module Sunspot
304
313
  # Sunspot::Setup:: New or existing setup for this class
305
314
  #
306
315
  def for!(clazz) #:nodoc:
307
- setups[clazz.name.to_sym] ||= new(clazz)
316
+ setup = setups[clazz.name.to_sym]
317
+ if setup && setup.class_object_id == clazz.object_id
318
+ setup
319
+ else
320
+ setups[clazz.name.to_sym] = new(clazz)
321
+ end
308
322
  end
309
323
 
310
324
  private