module Neo4j
module Core
module Traversal
class CypherQuery
include Enumerable
attr_accessor :query, :return_variable
def initialize(start_id, dir, types, query_hash=nil, &block)
this = self
rel_type = ":#{types.map{|x| "`#{x}`"}.join('|')}"
@query = Neo4j::Cypher.new do
default_ret = node(:default_ret)
n = node(start_id)
case dir
when :outgoing then
n > rel_type > default_ret
when :incoming then
n < rel_type < default_ret
when :both then
n - rel_type - default_ret
end
# where statement
ret_maybe = block && self.instance_exec(default_ret, &block)
ret = ret_maybe.respond_to?(:var_name) ? ret_maybe : default_ret
if query_hash
expr = []
query_hash.each{|pair| expr << (ret[pair[0]] == pair[1])}.to_a
expr.each_with_index do |obj, i|
Neo4j::Core::Cypher::ExprOp.new(obj, expr[i+1], "and") if i < expr.size - 1
end
end
this.return_variable = ret.var_name.to_sym
ret
end.to_s
end
def to_s
@query
end
def each
Neo4j._query(query).each do |r|
yield r[return_variable]
end
end
end
# By using this class you can both specify traversals and create new relationships.
# This object is return from the Neo4j::Core::Traversal methods.
# @see Neo4j::Core::Traversal#outgoing
# @see Neo4j::Core::Traversal#incoming
class Traverser
include Enumerable
include ToJava
def initialize(from, dir=:both, type=nil)
@from = from
@depth = 1
if type.nil?
raise "Traversing all relationship in direction #{dir.inspect} not supported, only :both supported" unless dir == :both
@td = Java::OrgNeo4jKernelImplTraversal::TraversalDescriptionImpl.new.breadth_first()
elsif (dir == :both)
both(type)
elsif (dir == :incoming)
incoming(type)
elsif (dir == :outgoing)
outgoing(type)
else
raise "Illegal direction #{dir.inspect}, expected :outgoing, :incoming or :both"
end
end
def query(query_hash = nil, &block)
# only one direction is supported
rel_types = [@outgoing_rel_types, @incoming_rel_types, @both_rel_types].find_all { |x| !x.nil? }
raise "Only one direction is allowed, outgoing:#{@outgoing_rel_types}, incoming:#{@incoming_rel_types}, @both:#{@both_rel_types}" if rel_types.count != 1
start_id = @from.neo_id
dir = (@outgoing_rel_types && :outgoing) || (@incoming_rel_types && :incoming) || (@both_rel_types && :both)
CypherQuery.new(start_id, dir, rel_types.first, query_hash, &block)
end
# Sets traversing depth first.
#
# The pre_or_post parameter parameter can have two values: :pre or :post
# @param [:pre, :post] pre_or_post
# * :pre - Traversing depth first, visiting each node before visiting its child nodes (default)
# * :post - Traversing depth first, visiting each node after visiting its child nodes.
# @return self
#
def depth_first(pre_or_post = :pre)
case pre_or_post
when :pre then
@td = @td.order(Java::OrgNeo4jKernel::Traversal.preorderDepthFirst())
when :post then
@td = @td.order(Java::OrgNeo4jKernel::Traversal.postorderDepthFirst())
else
raise "Unknown type #{pre_or_post}, should be :pre or :post"
end
self
end
# Sets traversing breadth first (default).
#
# This is the default ordering if none is defined.
# The pre_or_post parameter parameter can have two values: :pre or :post
# * :pre - Traversing breadth first, visiting each node before visiting its child nodes (default)
# * :post - Traversing breadth first, visiting each node after visiting its child nodes.
#
# @param [:pre, :post] pre_or_post The traversal order
# @return self
#
# === Note
# Please note that breadth first traversals have a higher memory overhead than depth first traversals.
# BranchSelectors carries state and hence needs to be uniquely instantiated for each traversal.
# Therefore it is supplied to the TraversalDescription through a BranchOrderingPolicy interface, which is a factory of BranchSelector instances.
def breadth_first(pre_or_post = :pre)
case pre_or_post
when :pre then
@td = @td.order(Java::OrgNeo4jKernel::Traversal.preorderBreadthFirst())
when :post then
@td = @td.order(Java::OrgNeo4jKernel::Traversal.postorderBreadthFirst())
else
raise "Unknown type #{pre_or_post}, should be :pre or :post"
end
self
end
def eval_paths(&eval_path_block)
@td = @td.evaluator(Evaluator.new(&eval_path_block))
self
end
# Sets the rules for how positions can be revisited during a traversal as stated in Uniqueness.
# @param [:node_global, :node_path, :node_recent, :none, :rel_global, :rel_path, :rel_recent] u the uniqueness option
# @return self
# @see Neo4j::Core::Traverser#unique
def unique(u = :node_global)
case u
when :node_global then
# A node cannot be traversed more than once.
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::NODE_GLOBAL)
when :node_path then
# For each returned node there 's a unique path from the start node to it.
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::NODE_PATH)
when :node_recent then
# This is like NODE_GLOBAL, but only guarantees uniqueness among the most recent visited nodes, with a configurable count.
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::NODE_RECENT)
when :none then
# No restriction (the user will have to manage it).
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::NONE)
when :rel_global then
# A relationship cannot be traversed more than once, whereas nodes can.
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::RELATIONSHIP_GLOBAL)
when :rel_path then
# No restriction (the user will have to manage it).
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::RELATIONSHIP_PATH)
when :rel_recent then
# Same as for NODE_RECENT, but for relationships.
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::RELATIONSHIP_RECENT)
else
raise "Got option for unique '#{u}' allowed: :node_global, :node_path, :node_recent, :none, :rel_global, :rel_path, :rel_recent"
end
self
end
def to_s
"NodeTraverser [from: #{@from.neo_id} depth: #{@depth}"
end
# Creates a new relationship between given node and self
# It can create more then one relationship
#
# @example One outgoing relationships
# node.outgoing(:foo) << other_node
#
# @example Two outgoing relationships
# node.outgoing(:foo).outgoing(:bar) << other_node
#
# @param [Neo4j::Node] other_node the node to which we want to create a relationship
# @return (see #new)
def <<(other_node)
new(other_node)
self
end
# Returns an real ruby array.
def to_ary
self.to_a
end
# Creates a new relationship between self and given node.
# It can create more then one relationship
# This method is used by the << operator.
#
# @example create one relationship
# node.outgoing(:bar).new(other_node, rel_props)
#
# @example two relationships
# node.outgoing(:bar).outgoing(:foo).new(other_node, rel_props)
#
# @example both incoming and outgoing - two relationships
# node.both(:bar).new(other_node, rel_props)
#
# @see #<<
# @param [Hash] props properties of new relationship
# @return [Neo4j::Relationship] the created relationship
def new(other_node, props = {})
@outgoing_rel_types && @outgoing_rel_types.each { |type| _new_out(other_node, type, props) }
@incoming_rel_types && @incoming_rel_types.each { |type| _new_in(other_node, type, props) }
@both_rel_types && @both_rel_types.each { |type| _new_both(other_node, type, props) }
end
# @private
def _new_out(other_node, type, props)
@from.create_relationship_to(other_node, type_to_java(type)).update(props)
end
# @private
def _new_in(other_node, type, props)
other_node.create_relationship_to(@from, type_to_java(type)).update(props)
end
# @private
def _new_both(other_node, type, props)
_new_out(other_node, type, props)
_new_in(other_node, type, props)
end
# @param (see Neo4j::Core::Traversal#both)
# @see Neo4j::Core::Traversal#both
def both(type)
@both_rel_types ||= []
@both_rel_types << type
_add_rel(:both, type)
self
end
# @param (see Neo4j::Core::Traversal#expand)
# @return self
# @see Neo4j::Core::Traversal#expand
def expander(&expander)
@td = @td.expand(RelExpander.create_pair(&expander))
self
end
# Adds one outgoing relationship type to the traversal
# @param (see Neo4j::Core::Traversal#outgoing)
# @return self
# @see Neo4j::Core::Traversal#outgoing
def outgoing(type)
@outgoing_rel_types ||= []
@outgoing_rel_types << type
_add_rel(:outgoing, type)
self
end
# Adds one incoming relationship type to the traversal
# @param (see Neo4j::Core::Traversal#incoming)
# @return self
# @see Neo4j::Core::Traversal#incoming
def incoming(type)
@incoming_rel_types ||= []
@incoming_rel_types << type
_add_rel(:incoming, type)
self
end
# @private
def _add_rel(dir, type)
t = type_to_java(type)
d = dir_to_java(dir)
@td = @td ? @td.relationships(t, d) : Java::OrgNeo4jKernelImplTraversal::TraversalDescriptionImpl.new.breadth_first().relationships(t, d)
end
# Cuts of of parts of the traversal.
# @yield [path]
# @yieldparam [Java::OrgNeo4jGraphdb::Path] path the path which can be used to filter nodes
# @yieldreturn [true,false] only if true the path should be cut of, no traversal beyond this.
# @example
# a.outgoing(:friends).outgoing(:recommend).depth(:all).prune{|path| path.end_node[:name] == 'B'}
# @see http://components.neo4j.org/neo4j/milestone/apidocs/org/neo4j/graphdb/Path.html
def prune(&block)
@td = @td.evaluator(PruneEvaluator.new(block))
self
end
# Only include nodes in the traversal in which the provided block returns true.
# @yield [path]
# @yieldparam [Java::OrgNeo4jGraphdb::Path] path the path which can be used to filter nodes
# @yieldreturn [true,false] only if true the node will be included in the traversal result.
#
# @example Return nodes that are exact at depth 2 from me
# a_node.outgoing(:friends).depth(2).filter{|path| path.length == 2}
# @see http://components.neo4j.org/neo4j/milestone/apidocs/org/neo4j/graphdb/Path.html
def filter(&block)
# we keep a reference to filter predicate since only one filter is allowed and we might want to modify it
@filter_predicate ||= FilterPredicate.new
@filter_predicate.add(block)
@td = @td.evaluator(@filter_predicate)
self
end
# Sets depth, if :all then it will traverse any depth
# @param [Fixnum, :all] d the depth of traversal, or all
# @return self
def depth(d)
@depth = d
self
end
# By default the start node is not included in the traversal
# Specifies that the start node should be included
# @return self
def include_start_node
@include_start_node = true
self
end
# @param [Fixnum] index the n'th node that will be return from the traversal
def [](index)
each_with_index { |node, i| break node if index == i }
end
# @return [true,false]
def empty?
first == nil
end
# Required by the Ruby Enumerable Mixin
def each
@raw ? iterator.each { |i| yield i } : iterator.each { |i| yield i.wrapper }
end
# Same as #each but does not wrap each node in a Ruby class, yields the Java Neo4j Node instance instead.
def each_raw
iterator.each { |i| yield i }
end
# Returns an enumerable of relationships instead of nodes
# @return self
def rels
@traversal_result = :rels
self
end
# If this is called then it will not wrap the nodes but instead return the raw Java Neo4j::Node objects when traversing
# @return self
def raw
@raw = true
self
end
# Specifies that we should return an enumerable of paths instead of nodes.
# @return self
def paths
@traversal_result = :paths
@raw = true
self
end
# @return the java iterator
def iterator
unless @include_start_node
@td = @td.evaluator(Java::OrgNeo4jGraphdbTraversal::Evaluators.exclude_start_position)
end
@td = @td.evaluator(Java::OrgNeo4jGraphdbTraversal::Evaluators.toDepth(@depth)) unless @depth == :all
if @traversal_result == :rels
@td.traverse(@from._java_node).relationships
elsif @traversal_result == :paths
@td.traverse(@from._java_node).iterator
else
@td.traverse(@from._java_node).nodes
end
end
end
end
end
end