Neo4j user-defined procedures

Categories: devenv, neo4j

I have been working on a project and part of the research includes the usage of Neo4J as one of the databases. I checked the usage of pure Cypher but trying to replicate a VP-tree on Neo4J using only Cypher didn’t prove performant enough.

We decided to give a try to use a Neo4j user-defined procedure to check if we could implement the same, having a procedure to do the heavy lifting of the tree handling.

As everything new I just tried to create a simple procedure that would create a node and see how that would work. As we would use the newest version of Neo4j (at the time of the writing 4.0.6), the github template for user produce isn’t up to date. The changes aren’t big, but I spent almost a day trying to figure out what would be wrong with my procedure, originally it looked like this:

package example;
import java.util.stream.Stream;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;
import org.neo4j.logging.Log;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;
public class CreateVPNode {
@Context
public GraphDatabaseService db;
@Context
public Log log;
@Procedure(name = "example.createNode", mode = Mode.WRITE)
public Stream createNode(@Name("nodeLabel") String _nodeLabel) {
try(Transaction tx = db.beginTx()) {
Label nodeLabel = Label.label(_nodeLabel);
Node rootNode = tx.createNode(nodeLabel);
tx.commit();
return Stream.of(rootNode);
}
}
}

As you can see the code was just creating a node with a given label and returning it, according to Neo4j documentation Node is a valid return type.. Unfortunately that isn’t true, at least on the version that I am testing.

When you create a procedure, a class ProcedureOutputSignatureCompiler tries to check the output, and among the check it go over the return class and its super classes, unfortunately Node is an interface and not an class, therefore the superclass chain ends up with a null, causing the code to throw a NullPointerException.

After checking the procedures of the neo4j-apoc-procedures project I noticed that all of them return a class called org.neo4j.procedure.builtin.BuiltInProcedures.NodeResult. That was the trick to make the code work.

Basically it would look like this:

package example;
import java.util.stream.Stream;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;
import org.neo4j.logging.Log;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;
import org.neo4j.procedure.builtin.BuiltInProcedures.NodeResult;
public class CreateVPNode {
@Context public GraphDatabaseService db;
@Context public Log log;
@Procedure(name = "example.createNode", mode = Mode.WRITE)
public Stream createNode(@Name("nodeLabel") String _nodeLabel) {
try (Transaction tx = db.beginTx()) {
Label nodeLabel = Label.label(_nodeLabel);
Node rootNode = tx.createNode(nodeLabel);
tx.commit();
return Stream.of(new NodeResult(rootNode)); }
}
}

I didn’t have the time to report and/or check if there is some hidden documentation about the proper object to use and/or return. Nevertheless I believe that it is important to document it.

A final note, if you want to use the procedure inside of Cypher and YIELD the result, you can do like this: CALL example.createNode(‘RootNode’) YIELD node RETURN labels(node). Notice that the name node is mandatory as it is the attribute of the returned object.

Here is the git repository with the code of the test procedure and the test: https://github.com/laerteocj/neo4j-user-procedure-example

«
»

    Leave a Reply

    Your email address will not be published. Required fields are marked *