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)
q = Neo4j::Cypher.query do
n = node(start_id)
next_nodes = case dir
when :outgoing then
n.outgoing(types)
when :incoming then
n.incoming(types)
when :both then
n.both(types)
end
if block
self.instance_exec(next_nodes, &block)
else
query_hash.each{|pair| next_nodes[pair[0]] == pair[1]}.to_a if query_hash
next_nodes
end
end
@query = q.to_s
self.return_variable=q.return_names.last
end
def to_s
@query.to_s
end
def each
Neo4j._query(to_s).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)
exp = RelExpander.create_pair(&expander)
@td = Java::Neo4jRb::Adaptor.expandPath(@td.java_object, exp)
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.
#
# The optional @branch_state@ parameter is an accessor for a state associated with a TraversalBranch during a traversal.
# TraversalBranch can have an associated state which follows down the branch as the traversal goes. If the state is modified with setState(Object) it means that branches further down will have the newly set state, until it potentially gets overridden again. The state returned from getState() represents the state associated with the parent branch, which by this point has followed down to the branch calling getState().
#
# @yield [path]
# @yieldparam [Java::OrgNeo4jGraphdb::Path] path the path which can be used to filter nodes
# @yieldparam [Java::org.neo4j.graphdb.traversal.BranchState] branch_state 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.
#
# The optional @branch_state@ parameter is an accessor for a state associated with a TraversalBranch during a traversal.
# TraversalBranch can have an associated state which follows down the branch as the traversal goes. If the state is modified with setState(Object) it means that branches further down will have the newly set state, until it potentially gets overridden again. The state returned from getState() represents the state associated with the parent branch, which by this point has followed down to the branch calling getState().
#
# @yield [path]
# @yieldparam [Java::OrgNeo4jGraphdb::Path] path the path which can be used to filter nodes
# @yieldparam [Java::org.neo4j.graphdb.traversal.BranchState] branch_state 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