require File.join(File.dirname(__FILE__), 'abstract_session_proxy') module Sunspot module SessionProxy # # This is a generic abstract implementation of a session proxy that allows # Sunspot to be used with a distributed (sharded) Solr deployment. Concrete # subclasses should implement the #session_for method, which takes a # searchable object and returns a Session that points to the appropriate # Solr shard for that object. Subclasses should also implement the # #all_sessions object, which returns the collection of all sharded Session # objects. # # The class is initialized with a session that points to the Solr instance # used to perform searches. Searches will have the +:shards+ param injected, # containing references to the Solr instances returned by #all_sessions. # # For more on distributed search, see: # http://wiki.apache.org/solr/DistributedSearch # # The following methods are not supported (although subclasses may in some # cases be able to support them): # # * batch # * config # * remove_by_id # * remove_by_id! # * remove_all with an argument # * remove_all! with an argument # class ShardingSessionProxy < AbstractSessionProxy not_supported :batch, :config, :remove_by_id, :remove_by_id! # # +search_session+ is the session that should be used for searching. # def initialize(search_session = Sunspot.session.new) @search_session = search_session end # # Return the appropriate shard session for the object. # # Concrete subclasses must implement this method. # def session_for(object) raise NotImplementedError end # # Return all shard sessions. # # Concrete subclasses must implement this method. # def all_sessions raise NotImplementedError end # # See Sunspot.index # def index(*objects) using_sharded_session(objects) { |session, group| session.index(group) } end # # See Sunspot.index! # def index!(*objects) using_sharded_session(objects) { |session, group| session.index!(group) } end # # See Sunspot.remove # def remove(*objects) using_sharded_session(objects) { |session, group| session.remove(group) } end # # See Sunspot.remove! # def remove!(*objects) using_sharded_session(objects) { |session, group| session.remove!(group) } end # # If no argument is passed, behaves like Sunspot.remove_all # # If an argument is passed, will raise NotSupportedError, as the proxy # does not know which session(s) to which to delegate this operation. # def remove_all(clazz = nil) if clazz raise NotSupportedError, "Sharding session proxy does not support remove_all with an argument." else all_sessions.each { |session| session.remove_all } end end # # If no argument is passed, behaves like Sunspot.remove_all! # # If an argument is passed, will raise NotSupportedError, as the proxy # does not know which session(s) to which to delegate this operation. # def remove_all!(clazz = nil) if clazz raise NotSupportedError, "Sharding session proxy does not support remove_all! with an argument." else all_sessions.each { |session| session.remove_all! } end end # # Commit all shards. See Sunspot.commit # def commit all_sessions.each { |session| session.commit } end # # Optimize all shards. See Sunspot.optimize # def optimize all_sessions.each { |session| session.optimize } end # # Commit all dirty sessions. Only dirty sessions will be committed. # # See Sunspot.commit_if_dirty # def commit_if_dirty all_sessions.each { |session| session.commit_if_dirty } end # # Commit all delete-dirty sessions. Only delete-dirty sessions will be # committed. # # See Sunspot.commit_if_delete_dirty # def commit_if_delete_dirty all_sessions.each { |session| session.commit_if_delete_dirty } end # # Instantiate a new Search object, but don't execute it. The search will # have an extra :shards param injected into the query, which will tell the # Solr instance referenced by the search session to search across all # shards. # # See Sunspot.new_search # def new_search(*types) shard_urls = all_sessions.map { |session| session.config.solr.url } search = @search_session.new_search(*types) search.build do adjust_solr_params { |params| params[:shards] = shard_urls.join(',') } # I feel a little dirty doing this. end search end # # Build and execute a new Search. The search will have an extra :shards # param injected into the query, which will tell the Solr instance # referenced by the search session to search across all shards. # # See Sunspot.search # def search(*types, &block) new_search(*types).execute end def more_like_this(object, &block) #FIXME should use shards new_more_like_this(object, &block).execute end def new_more_like_this(object, &block) @search_session.new_more_like_this(object, &block) end # # True if any shard session is dirty. Note that directly using the # #commit_if_dirty method is more efficient if that's what you're # trying to do, since in that case only the dirty sessions are committed. # # See Sunspot.dirty? # def dirty? all_sessions.any? { |session| session.dirty? } end # # True if any shard session is delete-dirty. Note that directly using the # #commit_if_delete_dirty method is more efficient if that's what you're # trying to do, since in that case only the delete-dirty sessions are # committed. # def delete_dirty? all_sessions.any? { |session| session.delete_dirty? } end private # # Group the objects by which shard session they correspond to, and yield # each session and is corresponding group of objects. # def using_sharded_session(objects) grouped_objects = Hash.new { |h, k| h[k] = [] } objects.flatten.each { |object| grouped_objects[session_for(object)] << object } grouped_objects.each_pair do |session, group| yield(session, group) end end end end end