How to return multiple nodes in Neo4j using Java plugin?

1.2k views Asked by At

I try to write a function in Java that finds nodes in my Neo4j graph database with same names and labels.

@UserFunction(value = "boris.example")
@Description("finds nodes with same name")

   public  ResourceIterator<Node> passName(@Name("nodeId") long nodeId)
   {
        Node nodeX = null;

        Node node = db.getNodeById( nodeId ); //Find node with spcific ID
        Object nameVal = node.getProperties("name"); //Get its name
        Label label = Label.label("VersionableObject"); //Decl. of label

        // Find nodes by label and name
        ResourceIterator<Node> nodes = db.findNodes(label, "name", nameVal); 
        nodes.close();

        return nodes;
   }

This function causes following exception an refuses to start Neo4j:

"Argument ResourceIterator<Node> at position 0 in passName with type Label cannot be converted to a Neo4j type: Don't know how to map org.neo4j.graphdb.ResourceIterator to the Neo4j Type System."

Then I tried to convert ResourceIterator to Stream using

Stream<Node> nodesStream = nodes.stream();

Same Error - Neo4j can't map Stream..

ResourceIterator<Node> nodes = db.findNodes(label, "name", nameVal);    

        while (nodes.hasNext()) {

            nodeX = nodes.next();

        }
        nodes.close();

return nodeX;

Output in Neo4j empty, even though it should find 3 nodes.

Converting the Stream to Array or List did not work as well.

Finding one node and displaying itself or its properties works, e.g.:

Node node = db.getNodeById( nodeId );
Map<String, Object> propertyMap = node.getProperties("name");

EDIT

After changing @UserFunction to @Procedure with mode.READ:

package org.neo4j.example.procedure_template;


import org.neo4j.graphdb.ResourceIterator;
import java.util.stream.Stream;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.logging.Log;
import org.neo4j.procedure.*;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;


public class FindNode {

    @Context
    public GraphDatabaseService db;

    @Context
    public Log log;


    public FindNode() {
    }

    @Procedure(value = "boris.getAllNodesWithProperty", mode = Mode.READ)
    @Description("boris.getAllNodesWithProperty - finds Node by ID and return defined Property")

       public Stream<Node> passName(@Name("nodeId") long nodeId)

       {
        Node node = db.getNodeById( nodeId );
        Object nameVal = node.getProperties("name");
        Label label = Label.label("VersionableObject");

        ResourceIterator<Node> nodes = db.findNodes(label, "name", nameVal);
        Stream<Node> nodesStream = nodes.stream();

        return nodesStream;
       }

} 

In this case i get: "Procedures with zero output fields must be declared as VOID".

Any ideas? Thank you!

1

There are 1 answers

2
logisima On BEST ANSWER

In Neo4j you can extend Cypher by creating some custom user defined function or procedure.

A user defined function is just a convertor, it's readonly, and return a single type that can be : long, Long, double, Double, boolean, Boolean, String, Node, Relationship, Path, Map<String, Object, or List<T>

A procedure can takes arguments, perform operations on the database, and return results (as a Stream).

In your code, you have defined a user function, and I think what you want is a procedure. That's why you have the cast error from ResourceIterator to the Neo4j Type System.

You have to make those changes :

  • replace @UserFunction(value = "boris.example") by @Procedure(value = "boris.example", mode = Mode.READ)
  • Change the return type of your method to Stream<Node>

Cheers.

Edit

A procedure must return a pojo or a simple type. So you have to wrap your Node in a pojo like this :

public class FindNode {

    @Context
    public GraphDatabaseService db;

    @Context
    public Log log;


    @Procedure(value = "boris.getAllNodesWithProperty", mode = Mode.READ)
    @Description("boris.getAllNodesWithProperty - finds Node by ID and return defined Property")
    public Stream<NodeResult> passName(@Name("nodeId") long nodeId)

    {
        Node node = db.getNodeById( nodeId );
        Object nameVal = node.getProperty("name");
        Label label = Label.label("VersionableObject");

        ResourceIterator<Node> nodes = db.findNodes(label, "name", nameVal);

        return nodes.stream().map( item -> new NodeResult(item));
    }

    public class NodeResult {

        public final Node node;

        public NodeResult(Node node) {
            this.node = node;
        }
    }


}