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