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.
# Notice that if you want to numerical range queries then you should specify a field_type of either Fixnum or Float.
# The index type will by default be :exact.
# Index on property arrays are supported.
#
# @example
# MyIndex.index(:age, :field_type => Fixnum) # default :exact
# MyIndex.index(:wheels, :field_type => Fixnum)
# MyIndex.index(:description, :type => :fulltext)
#
# @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)
if (java_array?(value))
conv_value = value.map{|x| indexed_value_for(field, x)}.to_java(Java::OrgNeo4jIndexLucene::ValueContext)
else
conv_value = indexed_value_for(field, value)
end
index = index_for_field(field.to_s)
index.add(entity, field, conv_value)
end
def java_array?(value)
value.respond_to?(:java_class) && value.java_class.to_s[0..0] == '['
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)
#return value.each {|x| rm_index(entity, field, x)} if value.respond_to?(:each)
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 "First item #{query.first}"}
#
# @example using an exact lucene index
# query = Person.find('name: kalle')
# puts "First item #{query.first}"
# query.close
#
# @example using an fulltext lucene index
# query = Person.find('name: kalle', :type => :fulltext)
# puts "First item #{query.first}"
# query.close
#
# @example Sorting, descending by one property
# Person.find({:name => 'kalle'}, :sort => {:name => :desc})
#
# @example Sorting using the builder pattern
# Person.find(:name => 'kalle').asc(:name)
#
# @example Searching by a set of values, OR search
# Person.find(:name => ['kalle', 'sune', 'jimmy'])
#
# @example Compound queries and Range queries
# Person.find('name: pelle').and(:age).between(2, 5)
# Person.find(:name => 'kalle', :age => (2..5))
# Person.find("name: 'asd'").and(:wheels => 8)
#
# @example Using the lucene java object
# # using the Neo4j query method directly
# # see, http://api.neo4j.org/1.6.1/org/neo4j/graphdb/index/ReadableIndex.html#query(java.lang.Object)
# MyIndex.find('description: "hej"', :type => :fulltext, :wrapped => false).get_single
#
# @param [String, Hash] query the lucene query
# @param [Hash] params lucene configuration parameters
# @return [Neo4j::Core::Index::LuceneQuery] a query object which uses the builder pattern for creating compound and sort queries.
# @note You must specify the index type :fulltext) if the property is index using that index (default is :exact)
def find(query, params = {})
index = index_for_type(params[:type] || :exact)
query.delete(:sort) if query.is_a?(Hash) && query.include?(:sort)
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
# Add the entity to this index for the given key/value pair if this particular key/value pair doesn't already exist.
# This ensures that only one entity will be associated with the key/value pair even if multiple transactions are trying to add it at the same time.
# One of those transactions will win and add it while the others will block, waiting for the winning transaction to finish.
# If the winning transaction was successful these other transactions will return the associated entity instead of adding it.
# If it wasn't successful the waiting transactions will begin a new race to add it.
#
# @param [Neo4j::Node, Neo4j::Relationship] entity the entity (i.e Node or Relationship) to associate the key/value pair with.
# @param [String, Symbol] key the key in the key/value pair to associate with the entity.
# @param [String, Fixnum, Float] value the value in the key/value pair to associate with the entity.
# @param [Symbol] index_type the type of lucene index
# @return [nil, Neo4j:Node, Neo4j::Relationship] the previously indexed entity, or nil if no entity was indexed before (and the specified entity was added to the index).
# @see Neo4j::Core::Index::UniqueFactory as an alternative which probably simplify creating unique entities
def put_if_absent(entity, key, value, index_type = :exact)
index = index_for_type(index_type)
index.put_if_absent(entity, key.to_s, value)
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