gojee-sunspot 2.0.3 → 2.0.4
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.
- data/.gitignore +12 -0
- data/Gemfile +5 -0
- data/History.txt +252 -0
- data/LICENSE +18 -0
- data/Rakefile +13 -0
- data/TODO +13 -0
- data/lib/light_config.rb +40 -0
- data/lib/sunspot/adapters.rb +265 -0
- data/lib/sunspot/batcher.rb +62 -0
- data/lib/sunspot/class_set.rb +23 -0
- data/lib/sunspot/composite_setup.rb +202 -0
- data/lib/sunspot/configuration.rb +53 -0
- data/lib/sunspot/data_extractor.rb +50 -0
- data/lib/sunspot/dsl/adjustable.rb +47 -0
- data/lib/sunspot/dsl/field_group.rb +57 -0
- data/lib/sunspot/dsl/field_query.rb +327 -0
- data/lib/sunspot/dsl/fields.rb +103 -0
- data/lib/sunspot/dsl/fulltext.rb +243 -0
- data/lib/sunspot/dsl/function.rb +27 -0
- data/lib/sunspot/dsl/functional.rb +44 -0
- data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
- data/lib/sunspot/dsl/paginatable.rb +32 -0
- data/lib/sunspot/dsl/query_facet.rb +36 -0
- data/lib/sunspot/dsl/restriction.rb +25 -0
- data/lib/sunspot/dsl/restriction_with_near.rb +160 -0
- data/lib/sunspot/dsl/scope.rb +217 -0
- data/lib/sunspot/dsl/search.rb +30 -0
- data/lib/sunspot/dsl/standard_query.rb +123 -0
- data/lib/sunspot/dsl.rb +5 -0
- data/lib/sunspot/field.rb +193 -0
- data/lib/sunspot/field_factory.rb +129 -0
- data/lib/sunspot/indexer.rb +136 -0
- data/lib/sunspot/query/abstract_field_facet.rb +52 -0
- data/lib/sunspot/query/bbox.rb +15 -0
- data/lib/sunspot/query/boost_query.rb +24 -0
- data/lib/sunspot/query/common_query.rb +96 -0
- data/lib/sunspot/query/composite_fulltext.rb +36 -0
- data/lib/sunspot/query/connective.rb +206 -0
- data/lib/sunspot/query/date_field_facet.rb +14 -0
- data/lib/sunspot/query/dismax.rb +132 -0
- data/lib/sunspot/query/field_facet.rb +41 -0
- data/lib/sunspot/query/field_group.rb +36 -0
- data/lib/sunspot/query/filter.rb +38 -0
- data/lib/sunspot/query/function_query.rb +52 -0
- data/lib/sunspot/query/geo.rb +53 -0
- data/lib/sunspot/query/geofilt.rb +16 -0
- data/lib/sunspot/query/highlighting.rb +62 -0
- data/lib/sunspot/query/more_like_this.rb +61 -0
- data/lib/sunspot/query/more_like_this_query.rb +12 -0
- data/lib/sunspot/query/pagination.rb +42 -0
- data/lib/sunspot/query/query_facet.rb +16 -0
- data/lib/sunspot/query/restriction.rb +262 -0
- data/lib/sunspot/query/scope.rb +9 -0
- data/lib/sunspot/query/sort.rb +109 -0
- data/lib/sunspot/query/sort_composite.rb +34 -0
- data/lib/sunspot/query/standard_query.rb +16 -0
- data/lib/sunspot/query/text_field_boost.rb +17 -0
- data/lib/sunspot/query.rb +11 -0
- data/lib/sunspot/schema.rb +151 -0
- data/lib/sunspot/search/abstract_search.rb +281 -0
- data/lib/sunspot/search/date_facet.rb +35 -0
- data/lib/sunspot/search/facet_row.rb +27 -0
- data/lib/sunspot/search/field_facet.rb +88 -0
- data/lib/sunspot/search/field_group.rb +32 -0
- data/lib/sunspot/search/group.rb +50 -0
- data/lib/sunspot/search/highlight.rb +38 -0
- data/lib/sunspot/search/hit.rb +150 -0
- data/lib/sunspot/search/hit_enumerable.rb +72 -0
- data/lib/sunspot/search/more_like_this_search.rb +31 -0
- data/lib/sunspot/search/paginated_collection.rb +57 -0
- data/lib/sunspot/search/query_facet.rb +67 -0
- data/lib/sunspot/search/standard_search.rb +21 -0
- data/lib/sunspot/search.rb +9 -0
- data/lib/sunspot/session.rb +262 -0
- data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
- data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
- data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
- data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
- data/lib/sunspot/session_proxy/multicore_session_proxy.rb +67 -0
- data/lib/sunspot/session_proxy/sharding_session_proxy.rb +222 -0
- data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
- data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
- data/lib/sunspot/session_proxy.rb +95 -0
- data/lib/sunspot/setup.rb +350 -0
- data/lib/sunspot/text_field_setup.rb +29 -0
- data/lib/sunspot/type.rb +393 -0
- data/lib/sunspot/util.rb +252 -0
- data/lib/sunspot/version.rb +3 -0
- data/lib/sunspot.rb +579 -0
- data/log/.gitignore +1 -0
- data/pkg/.gitignore +1 -0
- data/script/console +10 -0
- data/spec/api/adapters_spec.rb +33 -0
- data/spec/api/batcher_spec.rb +112 -0
- data/spec/api/binding_spec.rb +50 -0
- data/spec/api/class_set_spec.rb +24 -0
- data/spec/api/hit_enumerable_spec.rb +47 -0
- data/spec/api/indexer/attributes_spec.rb +149 -0
- data/spec/api/indexer/batch_spec.rb +72 -0
- data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
- data/spec/api/indexer/fixed_fields_spec.rb +57 -0
- data/spec/api/indexer/fulltext_spec.rb +43 -0
- data/spec/api/indexer/removal_spec.rb +53 -0
- data/spec/api/indexer/spec_helper.rb +1 -0
- data/spec/api/indexer_spec.rb +14 -0
- data/spec/api/query/advanced_manipulation_examples.rb +35 -0
- data/spec/api/query/connectives_examples.rb +189 -0
- data/spec/api/query/dsl_spec.rb +18 -0
- data/spec/api/query/dynamic_fields_examples.rb +165 -0
- data/spec/api/query/faceting_examples.rb +397 -0
- data/spec/api/query/fulltext_examples.rb +313 -0
- data/spec/api/query/function_spec.rb +79 -0
- data/spec/api/query/geo_examples.rb +68 -0
- data/spec/api/query/group_spec.rb +32 -0
- data/spec/api/query/highlighting_examples.rb +245 -0
- data/spec/api/query/more_like_this_spec.rb +140 -0
- data/spec/api/query/ordering_pagination_examples.rb +116 -0
- data/spec/api/query/scope_examples.rb +275 -0
- data/spec/api/query/spatial_examples.rb +27 -0
- data/spec/api/query/spec_helper.rb +1 -0
- data/spec/api/query/standard_spec.rb +29 -0
- data/spec/api/query/text_field_scoping_examples.rb +30 -0
- data/spec/api/query/types_spec.rb +20 -0
- data/spec/api/search/dynamic_fields_spec.rb +33 -0
- data/spec/api/search/faceting_spec.rb +360 -0
- data/spec/api/search/highlighting_spec.rb +69 -0
- data/spec/api/search/hits_spec.rb +131 -0
- data/spec/api/search/paginated_collection_spec.rb +36 -0
- data/spec/api/search/results_spec.rb +72 -0
- data/spec/api/search/search_spec.rb +23 -0
- data/spec/api/search/spec_helper.rb +1 -0
- data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
- data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
- data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
- data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
- data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
- data/spec/api/session_proxy/spec_helper.rb +9 -0
- data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +39 -0
- data/spec/api/session_spec.rb +232 -0
- data/spec/api/spec_helper.rb +3 -0
- data/spec/api/sunspot_spec.rb +29 -0
- data/spec/ext.rb +11 -0
- data/spec/helpers/indexer_helper.rb +17 -0
- data/spec/helpers/integration_helper.rb +8 -0
- data/spec/helpers/mock_session_helper.rb +13 -0
- data/spec/helpers/query_helper.rb +26 -0
- data/spec/helpers/search_helper.rb +68 -0
- data/spec/integration/dynamic_fields_spec.rb +57 -0
- data/spec/integration/faceting_spec.rb +251 -0
- data/spec/integration/field_grouping_spec.rb +66 -0
- data/spec/integration/geospatial_spec.rb +85 -0
- data/spec/integration/highlighting_spec.rb +44 -0
- data/spec/integration/indexing_spec.rb +55 -0
- data/spec/integration/keyword_search_spec.rb +317 -0
- data/spec/integration/local_search_spec.rb +64 -0
- data/spec/integration/more_like_this_spec.rb +43 -0
- data/spec/integration/scoped_search_spec.rb +354 -0
- data/spec/integration/stored_fields_spec.rb +12 -0
- data/spec/integration/test_pagination.rb +43 -0
- data/spec/integration/unicode_spec.rb +15 -0
- data/spec/mocks/adapters.rb +32 -0
- data/spec/mocks/blog.rb +3 -0
- data/spec/mocks/comment.rb +21 -0
- data/spec/mocks/connection.rb +126 -0
- data/spec/mocks/mock_adapter.rb +30 -0
- data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
- data/spec/mocks/mock_record.rb +52 -0
- data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
- data/spec/mocks/photo.rb +11 -0
- data/spec/mocks/post.rb +86 -0
- data/spec/mocks/super_class.rb +2 -0
- data/spec/mocks/user.rb +13 -0
- data/spec/spec_helper.rb +40 -0
- data/sunspot.gemspec +42 -0
- data/tasks/rdoc.rake +27 -0
- data/tasks/schema.rake +19 -0
- data/tasks/todo.rake +4 -0
- metadata +261 -3
@@ -0,0 +1,66 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module SessionProxy
|
3
|
+
#
|
4
|
+
# An abstract subclass of ShardingSessionProxy that shards by class.
|
5
|
+
# Concrete subclasses should not override the #session_for method, but
|
6
|
+
# should instead implement the #session_for_class method. They must also
|
7
|
+
# still implement the #all_sessions method.
|
8
|
+
#
|
9
|
+
# Unlike its parent class, ClassShardingSessionProxy implements
|
10
|
+
# #remove_by_id and all flavors of #remove_all.
|
11
|
+
#
|
12
|
+
class ClassShardingSessionProxy < ShardingSessionProxy
|
13
|
+
#
|
14
|
+
# Remove the Session object pointing at the shard that indexes the given
|
15
|
+
# class.
|
16
|
+
#
|
17
|
+
# <strong>Concrete subclasses must implement this method.</strong>
|
18
|
+
#
|
19
|
+
def session_for_class(clazz)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# See Sunspot.remove_by_id
|
25
|
+
#
|
26
|
+
def remove_by_id(clazz, id)
|
27
|
+
session_for_class(clazz).remove_by_id(clazz, id)
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# See Sunspot.remove_by_id!
|
32
|
+
#
|
33
|
+
def remove_by_id!(clazz, id)
|
34
|
+
session_for_class(clazz).remove_by_id!(clazz, id)
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# See Sunspot.remove_all
|
39
|
+
#
|
40
|
+
def remove_all(clazz = nil)
|
41
|
+
if clazz
|
42
|
+
session_for_class(clazz).remove_all(clazz)
|
43
|
+
else
|
44
|
+
all_sessions.each { |session| session.remove_all }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# See Sunspot.remove_all!
|
50
|
+
#
|
51
|
+
def remove_all!(clazz = nil)
|
52
|
+
if clazz
|
53
|
+
session_for_class(clazz).remove_all!(clazz)
|
54
|
+
else
|
55
|
+
all_sessions.each { |session| session.remove_all! }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def session_for(object)
|
62
|
+
session_for_class(object.class)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module SessionProxy
|
3
|
+
#
|
4
|
+
# A concrete implementation of ShardingSessionProxy that determines the
|
5
|
+
# shard for a given object based on the hash of its class and ID.
|
6
|
+
#
|
7
|
+
# <strong>If you change the number of shard sessions that this proxy
|
8
|
+
# encapsulates, all objects will point to a different shard.</strong> If you
|
9
|
+
# plan on adding more shards over time, consider your own
|
10
|
+
# ShardingSessionProxy implementation that does not determine the session
|
11
|
+
# using modular arithmetic (e.g., IDs 1-10000 go to shard 1, 10001-20000 go
|
12
|
+
# to shard 2, etc.)
|
13
|
+
#
|
14
|
+
# This implementation will, on average, yield an even distribution of
|
15
|
+
# objects across shards.
|
16
|
+
#
|
17
|
+
# Unlike the abstract ShardingSessionProxy, this proxy supports the
|
18
|
+
# #remove_by_id method.
|
19
|
+
#
|
20
|
+
class IdShardingSessionProxy < ShardingSessionProxy
|
21
|
+
#
|
22
|
+
# The shard sessions encapsulated by this class.
|
23
|
+
#
|
24
|
+
attr_reader :sessions
|
25
|
+
alias_method :all_sessions, :sessions #:nodoc:
|
26
|
+
|
27
|
+
#
|
28
|
+
# Initialize with a search session (see ShardingSessionProxy.new) and a
|
29
|
+
# collection of one or more shard sessions. See note about changing the
|
30
|
+
# number of shard sessions in the documentation for this class.
|
31
|
+
#
|
32
|
+
def initialize(search_session, shard_sessions)
|
33
|
+
super(search_session)
|
34
|
+
@sessions = shard_sessions
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Return a session based on the hash of the class and ID, modulo the
|
39
|
+
# number of shard sessions.
|
40
|
+
#
|
41
|
+
def session_for(object) #:nodoc:
|
42
|
+
session_for_index_id(Adapters::InstanceAdapter.adapt(object).index_id)
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# See Sunspot.remove_by_id
|
47
|
+
#
|
48
|
+
def remove_by_id(clazz, id)
|
49
|
+
session_for_index_id(
|
50
|
+
Adapters::InstanceAdapter.index_id_for(clazz, id)
|
51
|
+
).remove_by_id(clazz, id)
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# See Sunspot.remove_by_id!
|
56
|
+
#
|
57
|
+
def remove_by_id!(clazz, id)
|
58
|
+
session_for_index_id(
|
59
|
+
Adapters::InstanceAdapter.index_id_for(clazz, id)
|
60
|
+
).remove_by_id!(clazz, id)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def session_for_index_id(index_id)
|
66
|
+
@sessions[id_hash(index_id) % @sessions.length]
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# This method is implemented explicitly instead of using String#hash to
|
71
|
+
# give predictable behavior across different Ruby interpreters.
|
72
|
+
#
|
73
|
+
if "".respond_to?(:bytes) # Ruby 1.9
|
74
|
+
def id_hash(id)
|
75
|
+
id.bytes.inject { |hash, byte| hash * 31 + byte }
|
76
|
+
end
|
77
|
+
else
|
78
|
+
def id_hash(id)
|
79
|
+
hash, i, len = 0, 0, id.length
|
80
|
+
while i < len
|
81
|
+
hash = hash * 31 + id[i]
|
82
|
+
i += 1
|
83
|
+
end
|
84
|
+
hash
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'abstract_session_proxy')
|
2
|
+
|
3
|
+
module Sunspot
|
4
|
+
module SessionProxy
|
5
|
+
#
|
6
|
+
# This session proxy implementation allows Sunspot to be used with a
|
7
|
+
# master/slave Solr deployment. All write methods are delegated to a master
|
8
|
+
# session, and read methods are delegated to a slave session.
|
9
|
+
#
|
10
|
+
class MasterSlaveSessionProxy < AbstractSessionProxy
|
11
|
+
#
|
12
|
+
# The session that connects to the master Solr instance.
|
13
|
+
#
|
14
|
+
attr_reader :master_session
|
15
|
+
#
|
16
|
+
# The session that connects to the slave Solr instance.
|
17
|
+
#
|
18
|
+
attr_reader :slave_session
|
19
|
+
|
20
|
+
delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty,
|
21
|
+
:config, :delete_dirty?, :dirty?, :index, :index!, :optimize, :remove,
|
22
|
+
:remove!, :remove_all, :remove_all!, :remove_by_id,
|
23
|
+
:remove_by_id!, :to => :master_session
|
24
|
+
delegate :new_search, :search, :new_more_like_this, :more_like_this, :to => :slave_session
|
25
|
+
|
26
|
+
def initialize(master_session, slave_session)
|
27
|
+
@master_session, @slave_session = master_session, slave_session
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# By default, return the configuration for the master session. If the
|
32
|
+
# +delegate+ param is +:slave+, then return config for the slave session.
|
33
|
+
#
|
34
|
+
def config(delegate = :master)
|
35
|
+
case delegate
|
36
|
+
when :master then @master_session.config
|
37
|
+
when :slave then @slave_session.config
|
38
|
+
else raise(ArgumentError, "Expected :master or :slave")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module SessionProxy
|
3
|
+
class MulticoreSessionProxy < AbstractSessionProxy
|
4
|
+
|
5
|
+
def initialize( cores )
|
6
|
+
@sessions = {}
|
7
|
+
@cores ||= []
|
8
|
+
( @cores << cores ).flatten!
|
9
|
+
cores.each do |core|
|
10
|
+
|
11
|
+
# can't find a better way grab the sunspot.yml configuration (:user_configuration is a private method)
|
12
|
+
modified_config = Marshal.load( Marshal.dump( Sunspot::Rails.configuration.send(:user_configuration) ) )
|
13
|
+
|
14
|
+
modified_config["solr"]["path"] = File.join(
|
15
|
+
( modified_config["solr"]["path"] || Sunspot::Rails.configuration.path ), core )
|
16
|
+
|
17
|
+
extended_config = Sunspot::Rails::ExtendedConfiguration.new(modified_config)
|
18
|
+
session = @sessions[core] = Sunspot::Rails.build_session(extended_config)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_accessor :selected_session
|
23
|
+
|
24
|
+
def method_missing(method, *args, &block)
|
25
|
+
inspectable_args = args.flatten
|
26
|
+
types = inspectable_args.select(&:searchable?) rescue []
|
27
|
+
types += inspectable_args.select{|a| a.class.searchable? rescue false }
|
28
|
+
|
29
|
+
self.selected_session = session_for_types(types) || self.selected_session
|
30
|
+
|
31
|
+
if self.selected_session.respond_to?(method)
|
32
|
+
begin
|
33
|
+
self.selected_session.send(method, *args, &block)
|
34
|
+
rescue Exception => e
|
35
|
+
(puts "method #{method} caused: #{e.message}")
|
36
|
+
end
|
37
|
+
else
|
38
|
+
# raise NoMethodError, "Method #{method} isn't defined on session"
|
39
|
+
# logger.info("do nothing")
|
40
|
+
# super
|
41
|
+
# self.selected_session.send(method, *args, &block)
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
def infer_configuration_options_from_env_url( options )
|
48
|
+
if ENV["SOLR_URL"]
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def session_for_types(*types)
|
54
|
+
cores = types.flatten.map{|t| t.sunspot_options[:core] }.uniq
|
55
|
+
raise ArgumentError, "Can't mix and match types that use different solr cores" if cores.length > 1
|
56
|
+
|
57
|
+
@sessions[cores.first]
|
58
|
+
end
|
59
|
+
|
60
|
+
def session_for_objects(*objects)
|
61
|
+
types = objects.map(&:class).uniq
|
62
|
+
session_for_types(types)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,222 @@
|
|
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
|
+
# Optimize all shards. See Sunspot.optimize
|
124
|
+
#
|
125
|
+
def optimize
|
126
|
+
all_sessions.each { |session| session.optimize }
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# Commit all dirty sessions. Only dirty sessions will be committed.
|
131
|
+
#
|
132
|
+
# See Sunspot.commit_if_dirty
|
133
|
+
#
|
134
|
+
def commit_if_dirty
|
135
|
+
all_sessions.each { |session| session.commit_if_dirty }
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Commit all delete-dirty sessions. Only delete-dirty sessions will be
|
140
|
+
# committed.
|
141
|
+
#
|
142
|
+
# See Sunspot.commit_if_delete_dirty
|
143
|
+
#
|
144
|
+
def commit_if_delete_dirty
|
145
|
+
all_sessions.each { |session| session.commit_if_delete_dirty }
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Instantiate a new Search object, but don't execute it. The search will
|
150
|
+
# have an extra :shards param injected into the query, which will tell the
|
151
|
+
# Solr instance referenced by the search session to search across all
|
152
|
+
# shards.
|
153
|
+
#
|
154
|
+
# See Sunspot.new_search
|
155
|
+
#
|
156
|
+
def new_search(*types)
|
157
|
+
shard_urls = all_sessions.map { |session| session.config.solr.url }
|
158
|
+
search = @search_session.new_search(*types)
|
159
|
+
search.build do
|
160
|
+
adjust_solr_params { |params| params[:shards] = shard_urls.join(',') }
|
161
|
+
# I feel a little dirty doing this.
|
162
|
+
end
|
163
|
+
search
|
164
|
+
end
|
165
|
+
|
166
|
+
#
|
167
|
+
# Build and execute a new Search. The search will have an extra :shards
|
168
|
+
# param injected into the query, which will tell the Solr instance
|
169
|
+
# referenced by the search session to search across all shards.
|
170
|
+
#
|
171
|
+
# See Sunspot.search
|
172
|
+
#
|
173
|
+
def search(*types, &block)
|
174
|
+
new_search(*types).execute
|
175
|
+
end
|
176
|
+
|
177
|
+
def more_like_this(object, &block)
|
178
|
+
#FIXME should use shards
|
179
|
+
new_more_like_this(object, &block).execute
|
180
|
+
end
|
181
|
+
|
182
|
+
def new_more_like_this(object, &block)
|
183
|
+
@search_session.new_more_like_this(object, &block)
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# True if any shard session is dirty. Note that directly using the
|
188
|
+
# #commit_if_dirty method is more efficient if that's what you're
|
189
|
+
# trying to do, since in that case only the dirty sessions are committed.
|
190
|
+
#
|
191
|
+
# See Sunspot.dirty?
|
192
|
+
#
|
193
|
+
def dirty?
|
194
|
+
all_sessions.any? { |session| session.dirty? }
|
195
|
+
end
|
196
|
+
|
197
|
+
#
|
198
|
+
# True if any shard session is delete-dirty. Note that directly using the
|
199
|
+
# #commit_if_delete_dirty method is more efficient if that's what you're
|
200
|
+
# trying to do, since in that case only the delete-dirty sessions are
|
201
|
+
# committed.
|
202
|
+
#
|
203
|
+
def delete_dirty?
|
204
|
+
all_sessions.any? { |session| session.delete_dirty? }
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
#
|
210
|
+
# Group the objects by which shard session they correspond to, and yield
|
211
|
+
# each session and is corresponding group of objects.
|
212
|
+
#
|
213
|
+
def using_sharded_session(objects)
|
214
|
+
grouped_objects = Hash.new { |h, k| h[k] = [] }
|
215
|
+
objects.flatten.each { |object| grouped_objects[session_for(object)] << object }
|
216
|
+
grouped_objects.each_pair do |session, group|
|
217
|
+
yield(session, group)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'abstract_session_proxy')
|
2
|
+
|
3
|
+
module Sunspot
|
4
|
+
module SessionProxy
|
5
|
+
class SilentFailSessionProxy < AbstractSessionProxy
|
6
|
+
|
7
|
+
attr_reader :search_session
|
8
|
+
|
9
|
+
delegate :new_search, :search, :config,
|
10
|
+
:new_more_like_this, :more_like_this,
|
11
|
+
:delete_dirty, :delete_dirty?,
|
12
|
+
:to => :search_session
|
13
|
+
|
14
|
+
def initialize(search_session = Sunspot.session)
|
15
|
+
@search_session = search_session
|
16
|
+
end
|
17
|
+
|
18
|
+
def rescued_exception(method, e)
|
19
|
+
$stderr.puts("Exception in #{method}: #{e.message}")
|
20
|
+
end
|
21
|
+
|
22
|
+
SUPPORTED_METHODS = [
|
23
|
+
:batch, :commit, :commit_if_dirty, :commit_if_delete_dirty, :dirty?,
|
24
|
+
:index!, :index, :optimize, :remove!, :remove, :remove_all!, :remove_all,
|
25
|
+
:remove_by_id!, :remove_by_id
|
26
|
+
]
|
27
|
+
|
28
|
+
SUPPORTED_METHODS.each do |method|
|
29
|
+
module_eval(<<-RUBY)
|
30
|
+
def #{method}(*args, &block)
|
31
|
+
begin
|
32
|
+
search_session.#{method}(*args, &block)
|
33
|
+
rescue => e
|
34
|
+
self.rescued_exception(:#{method}, e)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
RUBY
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
require File.join(File.dirname(__FILE__), 'abstract_session_proxy')
|
3
|
+
|
4
|
+
module Sunspot
|
5
|
+
module SessionProxy
|
6
|
+
#
|
7
|
+
# This class implements a session proxy that creates a different Session
|
8
|
+
# object for each thread. Any multithreaded application should use this
|
9
|
+
# proxy.
|
10
|
+
#
|
11
|
+
class ThreadLocalSessionProxy < AbstractSessionProxy
|
12
|
+
FINALIZER = Proc.new do |object_id|
|
13
|
+
Thread.current[:"sunspot_session_#{object_id}"] = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
# The configuration with which the thread-local sessions are initialized.
|
17
|
+
attr_reader :config
|
18
|
+
@@next_id = 0
|
19
|
+
|
20
|
+
delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty, :delete_dirty?, :dirty?, :index, :index!, :new_search, :optimize, :remove, :remove!, :remove_all, :remove_all!, :remove_by_id, :remove_by_id!, :search, :more_like_this, :new_more_like_this, :to => :session
|
21
|
+
|
22
|
+
#
|
23
|
+
# Optionally pass an existing Sunspot::Configuration object. If none is
|
24
|
+
# passed, a default configuration is used; it can then be modified using
|
25
|
+
# the #config attribute.
|
26
|
+
#
|
27
|
+
def initialize(config = Sunspot::Configuration.new)
|
28
|
+
@config = config
|
29
|
+
ObjectSpace.define_finalizer(self, FINALIZER)
|
30
|
+
end
|
31
|
+
|
32
|
+
def session #:nodoc:
|
33
|
+
Thread.current[:"sunspot_session_#{object_id}"] ||= Session.new(config)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,95 @@
|
|
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
|
+
:AbstractSessionProxy,
|
32
|
+
File.join(
|
33
|
+
File.dirname(__FILE__),
|
34
|
+
'session_proxy',
|
35
|
+
'abstract_session_proxy'
|
36
|
+
)
|
37
|
+
)
|
38
|
+
autoload(
|
39
|
+
:ThreadLocalSessionProxy,
|
40
|
+
File.join(
|
41
|
+
File.dirname(__FILE__),
|
42
|
+
'session_proxy',
|
43
|
+
'thread_local_session_proxy'
|
44
|
+
)
|
45
|
+
)
|
46
|
+
autoload(
|
47
|
+
:MasterSlaveSessionProxy,
|
48
|
+
File.join(
|
49
|
+
File.dirname(__FILE__),
|
50
|
+
'session_proxy',
|
51
|
+
'master_slave_session_proxy'
|
52
|
+
)
|
53
|
+
)
|
54
|
+
autoload(
|
55
|
+
:ShardingSessionProxy,
|
56
|
+
File.join(
|
57
|
+
File.dirname(__FILE__),
|
58
|
+
'session_proxy',
|
59
|
+
'sharding_session_proxy'
|
60
|
+
)
|
61
|
+
)
|
62
|
+
autoload(
|
63
|
+
:ClassShardingSessionProxy,
|
64
|
+
File.join(
|
65
|
+
File.dirname(__FILE__),
|
66
|
+
'session_proxy',
|
67
|
+
'class_sharding_session_proxy'
|
68
|
+
)
|
69
|
+
)
|
70
|
+
autoload(
|
71
|
+
:IdShardingSessionProxy,
|
72
|
+
File.join(
|
73
|
+
File.dirname(__FILE__),
|
74
|
+
'session_proxy',
|
75
|
+
'id_sharding_session_proxy'
|
76
|
+
)
|
77
|
+
)
|
78
|
+
autoload(
|
79
|
+
:SilentFailSessionProxy,
|
80
|
+
File.join(
|
81
|
+
File.dirname(__FILE__),
|
82
|
+
'session_proxy',
|
83
|
+
'silent_fail_session_proxy'
|
84
|
+
)
|
85
|
+
)
|
86
|
+
autoload(
|
87
|
+
:MulticoreSessionProxy,
|
88
|
+
File.join(
|
89
|
+
File.dirname(__FILE__),
|
90
|
+
'session_proxy',
|
91
|
+
'multicore_session_proxy'
|
92
|
+
)
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|