pduey-sunspot 1.2.1.1
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 +225 -0
- data/LICENSE +18 -0
- data/Rakefile +15 -0
- data/TODO +13 -0
- data/VERSION.yml +4 -0
- data/bin/sunspot-installer +19 -0
- data/installer/config/schema.yml +95 -0
- data/lib/light_config.rb +40 -0
- data/lib/sunspot.rb +568 -0
- data/lib/sunspot/adapters.rb +265 -0
- data/lib/sunspot/composite_setup.rb +202 -0
- data/lib/sunspot/configuration.rb +46 -0
- data/lib/sunspot/data_extractor.rb +50 -0
- data/lib/sunspot/dsl.rb +5 -0
- data/lib/sunspot/dsl/adjustable.rb +47 -0
- data/lib/sunspot/dsl/field_query.rb +279 -0
- data/lib/sunspot/dsl/fields.rb +103 -0
- data/lib/sunspot/dsl/fulltext.rb +243 -0
- data/lib/sunspot/dsl/function.rb +14 -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 +28 -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 +121 -0
- data/lib/sunspot/dsl/scope.rb +217 -0
- data/lib/sunspot/dsl/search.rb +30 -0
- data/lib/sunspot/dsl/standard_query.rb +121 -0
- data/lib/sunspot/field.rb +193 -0
- data/lib/sunspot/field_factory.rb +129 -0
- data/lib/sunspot/indexer.rb +131 -0
- data/lib/sunspot/installer.rb +31 -0
- data/lib/sunspot/installer/library_installer.rb +45 -0
- data/lib/sunspot/installer/schema_builder.rb +219 -0
- data/lib/sunspot/installer/solrconfig_updater.rb +76 -0
- data/lib/sunspot/installer/task_helper.rb +18 -0
- data/lib/sunspot/java.rb +8 -0
- data/lib/sunspot/query.rb +11 -0
- data/lib/sunspot/query/abstract_field_facet.rb +52 -0
- data/lib/sunspot/query/boost_query.rb +24 -0
- data/lib/sunspot/query/common_query.rb +85 -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 +128 -0
- data/lib/sunspot/query/field_facet.rb +41 -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/highlighting.rb +55 -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 +38 -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 +95 -0
- data/lib/sunspot/query/sort_composite.rb +33 -0
- data/lib/sunspot/query/standard_query.rb +16 -0
- data/lib/sunspot/query/text_field_boost.rb +17 -0
- data/lib/sunspot/schema.rb +151 -0
- data/lib/sunspot/search.rb +9 -0
- data/lib/sunspot/search/abstract_search.rb +335 -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/highlight.rb +38 -0
- data/lib/sunspot/search/hit.rb +150 -0
- data/lib/sunspot/search/more_like_this_search.rb +31 -0
- data/lib/sunspot/search/paginated_collection.rb +55 -0
- data/lib/sunspot/search/query_facet.rb +67 -0
- data/lib/sunspot/search/standard_search.rb +21 -0
- data/lib/sunspot/session.rb +260 -0
- data/lib/sunspot/session_proxy.rb +87 -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/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/setup.rb +350 -0
- data/lib/sunspot/text_field_setup.rb +29 -0
- data/lib/sunspot/type.rb +372 -0
- data/lib/sunspot/util.rb +243 -0
- data/lib/sunspot/version.rb +3 -0
- data/pduey-sunspot.gemspec +38 -0
- data/script/console +10 -0
- data/spec/api/adapters_spec.rb +33 -0
- data/spec/api/binding_spec.rb +50 -0
- data/spec/api/indexer/attributes_spec.rb +149 -0
- data/spec/api/indexer/batch_spec.rb +46 -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 +70 -0
- data/spec/api/query/geo_examples.rb +68 -0
- data/spec/api/query/highlighting_examples.rb +223 -0
- data/spec/api/query/more_like_this_spec.rb +140 -0
- data/spec/api/query/ordering_pagination_examples.rb +95 -0
- data/spec/api/query/scope_examples.rb +275 -0
- data/spec/api/query/spec_helper.rb +1 -0
- data/spec/api/query/standard_spec.rb +28 -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 +26 -0
- data/spec/api/search/results_spec.rb +66 -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 +220 -0
- data/spec/api/spec_helper.rb +3 -0
- data/spec/api/sunspot_spec.rb +18 -0
- data/spec/ext.rb +11 -0
- data/spec/helpers/indexer_helper.rb +29 -0
- data/spec/helpers/query_helper.rb +38 -0
- data/spec/helpers/search_helper.rb +80 -0
- data/spec/integration/dynamic_fields_spec.rb +57 -0
- data/spec/integration/faceting_spec.rb +238 -0
- data/spec/integration/highlighting_spec.rb +24 -0
- data/spec/integration/indexing_spec.rb +33 -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/spec_helper.rb +7 -0
- data/spec/integration/stored_fields_spec.rb +12 -0
- data/spec/integration/test_pagination.rb +32 -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 +85 -0
- data/spec/mocks/super_class.rb +2 -0
- data/spec/mocks/user.rb +13 -0
- data/spec/spec_helper.rb +28 -0
- data/tasks/rdoc.rake +27 -0
- data/tasks/schema.rake +19 -0
- data/tasks/todo.rake +4 -0
- metadata +369 -0
|
@@ -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,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,350 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
#
|
|
3
|
+
# This class encapsulates the search/indexing setup for a given class. Its
|
|
4
|
+
# contents are built using the Sunspot.setup method.
|
|
5
|
+
#
|
|
6
|
+
class Setup #:nodoc:
|
|
7
|
+
attr_reader :class_object_id
|
|
8
|
+
def initialize(clazz)
|
|
9
|
+
@class_object_id = clazz.object_id
|
|
10
|
+
@class_name = clazz.name
|
|
11
|
+
@field_factories, @text_field_factories, @dynamic_field_factories,
|
|
12
|
+
@field_factories_cache, @text_field_factories_cache,
|
|
13
|
+
@dynamic_field_factories_cache = *Array.new(6) { Hash.new }
|
|
14
|
+
@stored_field_factories_cache = Hash.new { |h, k| h[k] = [] }
|
|
15
|
+
@more_like_this_field_factories_cache = Hash.new { |h, k| h[k] = [] }
|
|
16
|
+
@dsl = DSL::Fields.new(self)
|
|
17
|
+
add_field_factory(:class, Type::ClassType.instance)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def type_names
|
|
21
|
+
[@class_name]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
#
|
|
25
|
+
# Add field factory for scope/ordering
|
|
26
|
+
#
|
|
27
|
+
def add_field_factory(name, type, options = {}, &block)
|
|
28
|
+
stored, more_like_this = options[:stored], options[:more_like_this]
|
|
29
|
+
field_factory = FieldFactory::Static.new(name, type, options, &block)
|
|
30
|
+
@field_factories[field_factory.signature] = field_factory
|
|
31
|
+
@field_factories_cache[field_factory.name] = field_factory
|
|
32
|
+
if stored
|
|
33
|
+
@stored_field_factories_cache[field_factory.name] << field_factory
|
|
34
|
+
end
|
|
35
|
+
if more_like_this
|
|
36
|
+
@more_like_this_field_factories_cache[field_factory.name] << field_factory
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
# Add field_factories for fulltext search
|
|
42
|
+
#
|
|
43
|
+
# ==== Parameters
|
|
44
|
+
#
|
|
45
|
+
# field_factories<Array>:: Array of Sunspot::Field objects
|
|
46
|
+
#
|
|
47
|
+
def add_text_field_factory(name, options = {}, &block)
|
|
48
|
+
stored, more_like_this = options[:stored], options[:more_like_this]
|
|
49
|
+
field_factory = FieldFactory::Static.new(name, Type::TextType.instance, options, &block)
|
|
50
|
+
@text_field_factories[name] = field_factory
|
|
51
|
+
@text_field_factories_cache[field_factory.name] = field_factory
|
|
52
|
+
if stored
|
|
53
|
+
@stored_field_factories_cache[field_factory.name] << field_factory
|
|
54
|
+
end
|
|
55
|
+
if more_like_this
|
|
56
|
+
@more_like_this_field_factories_cache[field_factory.name] << field_factory
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
#
|
|
61
|
+
# Add dynamic field_factories
|
|
62
|
+
#
|
|
63
|
+
# ==== Parameters
|
|
64
|
+
#
|
|
65
|
+
# field_factories<Array>:: Array of dynamic field objects
|
|
66
|
+
#
|
|
67
|
+
def add_dynamic_field_factory(name, type, options = {}, &block)
|
|
68
|
+
stored, more_like_this = options[:stored], options[:more_like_this]
|
|
69
|
+
field_factory = FieldFactory::Dynamic.new(name, type, options, &block)
|
|
70
|
+
@dynamic_field_factories[field_factory.signature] = field_factory
|
|
71
|
+
@dynamic_field_factories_cache[field_factory.name] = field_factory
|
|
72
|
+
if stored
|
|
73
|
+
@stored_field_factories_cache[field_factory.name] << field_factory
|
|
74
|
+
end
|
|
75
|
+
if more_like_this
|
|
76
|
+
@more_like_this_field_factories_cache[field_factory.name] << field_factory
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
#
|
|
81
|
+
# Add a document boost to documents at index time. Document boost can be
|
|
82
|
+
# static (the same for all documents of this class), or extracted on a per-
|
|
83
|
+
# document basis using either attribute or block extraction as per usual.
|
|
84
|
+
#
|
|
85
|
+
def add_document_boost(attr_name, &block)
|
|
86
|
+
@document_boost_extractor =
|
|
87
|
+
if attr_name
|
|
88
|
+
if attr_name.respond_to?(:to_f)
|
|
89
|
+
DataExtractor::Constant.new(attr_name)
|
|
90
|
+
else
|
|
91
|
+
DataExtractor::AttributeExtractor.new(attr_name)
|
|
92
|
+
end
|
|
93
|
+
else
|
|
94
|
+
DataExtractor::BlockExtractor.new(&block)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# Builder method for evaluating the setup DSL
|
|
100
|
+
#
|
|
101
|
+
def setup(&block)
|
|
102
|
+
Util.instance_eval_or_call(@dsl, &block)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#
|
|
106
|
+
# Return the Field with the given (public-facing) name
|
|
107
|
+
#
|
|
108
|
+
def field(field_name)
|
|
109
|
+
if field_factory = @field_factories_cache[field_name.to_sym]
|
|
110
|
+
field_factory.build
|
|
111
|
+
else
|
|
112
|
+
raise(
|
|
113
|
+
UnrecognizedFieldError,
|
|
114
|
+
"No field configured for #{@class_name} with name '#{field_name}'"
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#
|
|
120
|
+
# Return one or more text fields with the given public-facing name. This
|
|
121
|
+
# implementation will always return a single field (in an array), but
|
|
122
|
+
# CompositeSetup objects might return more than one.
|
|
123
|
+
#
|
|
124
|
+
def text_fields(field_name)
|
|
125
|
+
text_field =
|
|
126
|
+
if field_factory = @text_field_factories_cache[field_name.to_sym]
|
|
127
|
+
field_factory.build
|
|
128
|
+
else
|
|
129
|
+
raise(
|
|
130
|
+
UnrecognizedFieldError,
|
|
131
|
+
"No text field configured for #{@class_name} with name '#{field_name}'"
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
[text_field]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
#
|
|
138
|
+
# Return one or more stored fields (can be either attribute or text fields)
|
|
139
|
+
# for the given name.
|
|
140
|
+
#
|
|
141
|
+
def stored_fields(field_name, dynamic_field_name = nil)
|
|
142
|
+
@stored_field_factories_cache[field_name.to_sym].map do |field_factory|
|
|
143
|
+
if dynamic_field_name
|
|
144
|
+
field_factory.build(dynamic_field_name)
|
|
145
|
+
else
|
|
146
|
+
field_factory.build
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
#
|
|
152
|
+
# Return one or more more_like_this fields (can be either attribute or text fields)
|
|
153
|
+
# for the given name.
|
|
154
|
+
#
|
|
155
|
+
def more_like_this_fields(field_name)
|
|
156
|
+
@more_like_this_field_factories_cache[field_name.to_sym].map do |field_factory|
|
|
157
|
+
field_factory.build
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
#
|
|
162
|
+
# Return the DynamicFieldFactory with the given base name
|
|
163
|
+
#
|
|
164
|
+
def dynamic_field_factory(field_name)
|
|
165
|
+
@dynamic_field_factories_cache[field_name.to_sym] || raise(
|
|
166
|
+
UnrecognizedFieldError,
|
|
167
|
+
"No dynamic field configured for #{@class_name} with name '#{field_name}'"
|
|
168
|
+
)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
#
|
|
172
|
+
# Return all attribute fields
|
|
173
|
+
#
|
|
174
|
+
def fields
|
|
175
|
+
field_factories.map { |field_factory| field_factory.build }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
#
|
|
179
|
+
# Return all text fields
|
|
180
|
+
#
|
|
181
|
+
def all_text_fields
|
|
182
|
+
text_field_factories.map { |text_field_factory| text_field_factory.build }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
#
|
|
186
|
+
# Return all more_like_this fields
|
|
187
|
+
#
|
|
188
|
+
def all_more_like_this_fields
|
|
189
|
+
@more_like_this_field_factories_cache.values.map do |field_factories|
|
|
190
|
+
field_factories.map { |field_factory| field_factory.build }
|
|
191
|
+
end.flatten
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
#
|
|
195
|
+
# Get the field_factories associated with this setup as well as all inherited field_factories
|
|
196
|
+
#
|
|
197
|
+
# ==== Returns
|
|
198
|
+
#
|
|
199
|
+
# Array:: Collection of all field_factories associated with this setup
|
|
200
|
+
#
|
|
201
|
+
def field_factories
|
|
202
|
+
collection_from_inheritable_hash(:field_factories)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
#
|
|
206
|
+
# Get the text field_factories associated with this setup as well as all inherited
|
|
207
|
+
# text field_factories
|
|
208
|
+
#
|
|
209
|
+
# ==== Returns
|
|
210
|
+
#
|
|
211
|
+
# Array:: Collection of all text field_factories associated with this setup
|
|
212
|
+
#
|
|
213
|
+
def text_field_factories
|
|
214
|
+
collection_from_inheritable_hash(:text_field_factories)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
#
|
|
218
|
+
# Get all static, dynamic, and text field_factories associated with this setup as
|
|
219
|
+
# well as all inherited field_factories
|
|
220
|
+
#
|
|
221
|
+
# ==== Returns
|
|
222
|
+
#
|
|
223
|
+
# Array:: Collection of all text and scope field_factories associated with this setup
|
|
224
|
+
#
|
|
225
|
+
def all_field_factories
|
|
226
|
+
all_field_factories = []
|
|
227
|
+
all_field_factories.concat(field_factories).concat(text_field_factories).concat(dynamic_field_factories)
|
|
228
|
+
all_field_factories
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
#
|
|
232
|
+
# Get all dynamic field_factories for this and parent setups
|
|
233
|
+
#
|
|
234
|
+
# ==== Returns
|
|
235
|
+
#
|
|
236
|
+
# Array:: Dynamic field_factories
|
|
237
|
+
#
|
|
238
|
+
def dynamic_field_factories
|
|
239
|
+
collection_from_inheritable_hash(:dynamic_field_factories)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
#
|
|
243
|
+
# Return the class associated with this setup.
|
|
244
|
+
#
|
|
245
|
+
# ==== Returns
|
|
246
|
+
#
|
|
247
|
+
# clazz<Class>:: Class setup is configured for
|
|
248
|
+
#
|
|
249
|
+
def clazz
|
|
250
|
+
Util.full_const_get(@class_name)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
#
|
|
254
|
+
# Get the document boost for a given model
|
|
255
|
+
#
|
|
256
|
+
def document_boost_for(model)
|
|
257
|
+
if @document_boost_extractor
|
|
258
|
+
@document_boost_extractor.value_for(model)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
protected
|
|
263
|
+
|
|
264
|
+
#
|
|
265
|
+
# Get the nearest inherited setup, if any
|
|
266
|
+
#
|
|
267
|
+
# ==== Returns
|
|
268
|
+
#
|
|
269
|
+
# Sunspot::Setup:: Setup for the nearest ancestor of this setup's class
|
|
270
|
+
#
|
|
271
|
+
def parent
|
|
272
|
+
Setup.for(clazz.superclass)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def get_inheritable_hash(name)
|
|
276
|
+
hash = instance_variable_get(:"@#{name}")
|
|
277
|
+
parent.get_inheritable_hash(name).each_pair do |key, value|
|
|
278
|
+
hash[key] = value unless hash.has_key?(key)
|
|
279
|
+
end if parent
|
|
280
|
+
hash
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
private
|
|
284
|
+
|
|
285
|
+
def collection_from_inheritable_hash(name)
|
|
286
|
+
get_inheritable_hash(name).values
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
class <<self
|
|
290
|
+
#
|
|
291
|
+
# Retrieve or create the Setup instance for the given class, evaluating
|
|
292
|
+
# the given block to add to the setup's configuration
|
|
293
|
+
#
|
|
294
|
+
def setup(clazz, &block) #:nodoc:
|
|
295
|
+
self.for!(clazz).setup(&block)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
#
|
|
299
|
+
# Retrieve the setup instance for the given class, or for the nearest
|
|
300
|
+
# ancestor that has a setup, if any.
|
|
301
|
+
#
|
|
302
|
+
# ==== Parameters
|
|
303
|
+
#
|
|
304
|
+
# clazz<Class>:: Class for which to retrieve a setup
|
|
305
|
+
#
|
|
306
|
+
# ==== Returns
|
|
307
|
+
#
|
|
308
|
+
# Sunspot::Setup::
|
|
309
|
+
# Setup instance associated with the given class or its nearest ancestor
|
|
310
|
+
#
|
|
311
|
+
def for(clazz) #:nodoc:
|
|
312
|
+
setups[clazz.name.to_sym] || self.for(clazz.superclass) if clazz
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
protected
|
|
316
|
+
|
|
317
|
+
#
|
|
318
|
+
# Retrieve or create a Setup instance for this class
|
|
319
|
+
#
|
|
320
|
+
# ==== Parameters
|
|
321
|
+
#
|
|
322
|
+
# clazz<Class>:: Class for which to retrieve a setup
|
|
323
|
+
#
|
|
324
|
+
# ==== Returns
|
|
325
|
+
#
|
|
326
|
+
# Sunspot::Setup:: New or existing setup for this class
|
|
327
|
+
#
|
|
328
|
+
def for!(clazz) #:nodoc:
|
|
329
|
+
setup = setups[clazz.name.to_sym]
|
|
330
|
+
if setup && setup.class_object_id == clazz.object_id
|
|
331
|
+
setup
|
|
332
|
+
else
|
|
333
|
+
setups[clazz.name.to_sym] = new(clazz)
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
private
|
|
338
|
+
|
|
339
|
+
# Singleton hash of class names to Setup instances
|
|
340
|
+
#
|
|
341
|
+
# ==== Returns
|
|
342
|
+
#
|
|
343
|
+
# Hash:: Class names keyed to Setup instances
|
|
344
|
+
#
|
|
345
|
+
def setups
|
|
346
|
+
@setups ||= {}
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|