module Neo4j module Core module Index # This class is delegated from the Neo4j::Core::Index::ClassMethod # @see Neo4j::Core::Index::ClassMethods class Indexer # @return [Neo4j::Core::Index::IndexConfig] attr_reader :config def initialize(config) @config = config @indexes = {} # key = type, value = java neo4j index # to enable subclass indexing to work properly, store a list of parent indexers and # whenever an operation is performed on this one, perform it on all end def to_s "Indexer @#{object_id} index on: [#{@config.fields.map{|f| @config.numeric?(f)? "#{f} (numeric)" : f}.join(', ')}]" end # Add an index on a field so that it will be automatically updated by neo4j transactional events. # # The index method takes an optional configuration hash which allows you to: # # @example Add an index on an a property # # class Person # include Neo4j::NodeMixin # index :name # end # # When the property name is changed/deleted or the node created it will keep the lucene index in sync. # You can then perform a lucene query like this: Person.find('name: andreas') # # @example Add index on other nodes. # # class Person # include Neo4j::NodeMixin # has_n(:friends).to(Contact) # has_n(:known_by).from(:friends) # index :user_id, :via => :known_by # end # # Notice that you *must* specify an incoming relationship with the via key, as shown above. # In the example above an index user_id will be added to all Person nodes which has a friends relationship # that person with that user_id. This allows you to do lucene queries on your friends properties. # # @example Set the type value to index # # class Person # include Neo4j::NodeMixin # property :height, :weight, :type => Float # index :height, :weight # end # # By default all values will be indexed as Strings. # If you want for example to do a numerical range query you must tell Neo4j.rb to index it as a numeric value. # You do that with the key type on the property. # # Supported values for :type is String, Float, Date, DateTime and Fixnum # # === For more information # # @see Neo4j::Core::Index::LuceneQuery # @see #find # def index(*args) @config.index(args) end # @return [true,false] if there is an index on the given field. def index?(field) @config.index?(field) end # @return [true,false] if the def trigger_on?(props) @config.trigger_on?(props) end # @return [Symbol] the type of index for the given field (e.g. :exact or :fulltext) def index_type(field) @config.index_type(field) end # @return [true,false] if there is an index of the given type defined. def has_index_type?(type) @config.has_index_type?(type) end # Adds an index on the given entity # This is normally not needed since you can instead declare an index which will automatically keep # the lucene index in sync. # @see #index def add_index(entity, field, value) return false unless index?(field) conv_value = indexed_value_for(field, value) index = index_for_field(field.to_s) index.add(entity, field, conv_value) end # Removes an index on the given entity # This is normally not needed since you can instead declare an index which will automatically keep # the lucene index in sync. # @see #index def rm_index(entity, field, value) return false unless index?(field) index_for_field(field).remove(entity, field, value) end # Performs a Lucene Query. # # In order to use this you have to declare an index on the fields first, see #index. # Notice that you should close the lucene query after the query has been executed. # You can do that either by provide an block or calling the Neo4j::Core::Index::LuceneQuery#close # method. When performing queries from Ruby on Rails you do not need this since it will be automatically closed # (by Rack). # # @example with a block # # Person.find('name: kalle') {|query| puts "#{[*query].join(', )"} # # @example # # query = Person.find('name: kalle') # puts "First item #{query.first}" # query.close # # @return [Neo4j::Core::Index::LuceneQuery] a query object def find(query, params = {}) index = index_for_type(params[:type] || :exact) if query.is_a?(Hash) && (query.include?(:conditions) || query.include?(:sort)) params.merge! query.reject { |k, _| k == :conditions } query.delete(:sort) query = query.delete(:conditions) if query.include?(:conditions) end query = (params[:wrapped].nil? || params[:wrapped]) ? LuceneQuery.new(index, @config, query, params) : index.query(query) if block_given? begin ret = yield query ensure query.close end ret else query end end # Delete all index configuration. No more automatic indexing will be performed def rm_index_config @config.rm_index_config end # delete the index, if no type is provided clear all types of indexes def rm_index_type(type=nil) if type key = @config.index_name_for_type(type) @indexes[key] && @indexes[key].delete @indexes[key] = nil else @indexes.each_value { |index| index.delete } @indexes.clear end end # Called when the neo4j shutdown in order to release references to indexes def on_neo4j_shutdown @indexes.clear end # Called from the event handler when a new node or relationships is about to be committed. def update_index_on(node, field, old_val, new_val) if index?(field) rm_index(node, field, old_val) if old_val add_index(node, field, new_val) if new_val end end # Called from the event handler when deleting a property def remove_index_on(node, old_props) @config.fields.each { |field| rm_index(node, field, old_props[field]) if old_props[field] } end # Creates a wrapped ValueContext for the given value. Checks if it's numeric value in the configuration. # @return [Java::OrgNeo4jIndexLucene::ValueContext] a wrapped neo4j lucene value context def indexed_value_for(field, value) if @config.numeric?(field) Java::OrgNeo4jIndexLucene::ValueContext.new(value).indexNumeric else Java::OrgNeo4jIndexLucene::ValueContext.new(value) end end # @return [Java::OrgNeo4jGraphdb::Index] for the given field def index_for_field(field) type = @config.index_type(field) index_name = index_name_for_type(type) @indexes[index_name] ||= create_index_with(type, index_name) end # @return [Java::OrgNeo4jGraphdb::Index] for the given index type def index_for_type(type) index_name = index_name_for_type(type) @indexes[index_name] ||= create_index_with(type, index_name) end # @return [String] the name of the index which are stored on the filesystem def index_name_for_type(type) @config.index_name_for_type(type) end # @return [Hash] the lucene config for the given index type def lucene_config(type) conf = Neo4j::Config[:lucene][type.to_s] raise "unknown lucene type #{type}" unless conf conf end # Creates a new lucene index using the lucene configuration for the given index_name # # @param [:node, :relationship] type relationship or node index # @param [String] index_name the (file) name of the index # @return [Java::OrgNeo4jGraphdb::Index] for the given index type def create_index_with(type, index_name) db = Neo4j.started_db index_config = lucene_config(type) if config.entity_type == :node db.lucene.for_nodes(index_name, index_config) else db.lucene.for_relationships(index_name, index_config) end end end end end end