Can I use cypher in a user defined procedure?

The page on user defined procedures and the example therein show how you can use the core API to define user-defined functions.

Is it possible for me to invoke a cypher query within a user-define procedure, or am I limited to using just the core API / traversal framework?

While this doesn't answer the question I originally asked, I came across apoc.custom.asProcedure which is good enough for my use case.

I'll still leave this question open since I think for more complex requirements, it'll perhaps be needed.

You can use Cypher in your own user-defined function or procedure.

package mypackage;

import org.neo4j.graphdb.*;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.UserFunction;

public class MyFunctionClass
{
	@Context
    public GraphDatabaseService db;

    @UserFunction
    @Description("mypackage.myfunction('arg1') - do a thing")
    public Node myfunction(
            @Name("arg1") String arg1
    ){
		try (
		        Transaction tx = db.beginTx();
		        Result result = db.execute("MATCH (n:Node) RETURN n LIMIT 10")
		) {
		    while ( result.hasNext()) {
	            Map<String,Object> row = result.next();
	            for ( Map.Entry<String,Object> column : row.entrySet() ) {
	            	Object node = column.getValue();
	            	// here you'll have every match. 
	            	// note, things get trickier when you want to process multiple result columns. 
	            }
	        }

		    tx.success();
		} catch (Throwable e) {
		    throw new CypherException("Cypher.query failed: "+query, e);
		}
    }
}

@tony.chiboucas oh wow that's amazing. I thought that I'll have to initialize db myself. Can you tell me more about the @Context magic that's happening?

What are the other things which one can get with @Context?

I see that there is a list of @Context things right here: https://neo4j.com/docs/java-reference/current/extending-neo4j/procedures-and-functions/procedures/#injectable-resources

I somehow missed it initially!

The @Context is auto-wired dependency injection. It only works well with GraphDatabaseService. There are other parts of the Neo4j core you can get at with static methods and singletons, but beware, they don't work the way you'd expect.

Take a poke at my code, it's still a bit of a mess, but there's some gems in there:

1 Like

I would like to leave a note that

  @Context
  public Transaction tx;

might be a better idea than using the db directly. This is because if the calling code starts a transaction, and then your user-defined function (i don't know if this is a problem for procedures) starts another transaction then it causes some sort of transaction-nesting problem for neo4j

Do you have a way to execute Cypher, or query the database, that way? When I tried something like this, it only works if your function is standalone, and does not need to interact with anything other that the arguments it is provided.

I certainly agree that such functions are ideal, and IFF you can define your functions that way, with a Cypher friendly return, code and use of the function is much simpler.

Yes, of course you can execute your query using tx.execute. Was that your question?

Or are you asking if I'm able to execute cypher, as well as do other things in my function?

Yes, both... maybe a sample code-block? It's a bit of a chore unraveling how to do those things from Neo4j documentation.

Yes, both... maybe a sample code-block? It's a bit of a chore unraveling how to do those things from Neo4j documentation.

Unfortunately my usecase is rather simplistic, so either I have user-defined functions where I'm not using cypher at all or one where I only have one cypher query and I'm post processing the results in a way I find easier than doing in purely cypher-defined functions (e.g. apoc.custom.asFunction).

So I can't comment on if doing "other things" may work. As for executing cypher without injecting DB, I have amended your example to show what I meant.

package mypackage;

import org.neo4j.graphdb.*;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.UserFunction;

public class MyFunctionClass
{
 @Context
  public Transaction tx;

    @UserFunction
    @Description("mypackage.myfunction('arg1') - do a thing")
    public Node myfunction(
            @Name("arg1") String arg1
    ){
		try (
		        Result result = tx.execute("MATCH (n:Node) RETURN n LIMIT 10")
		) {
		    while ( result.hasNext()) {
	            Map<String,Object> row = result.next();
	            for ( Map.Entry<String,Object> column : row.entrySet() ) {
	            	Object node = column.getValue();
	            	// here you'll have every match. 
	            	// note, things get trickier when you want to process multiple result columns. 
	            }
	        }

		} catch (Throwable e) {
		    throw new CypherException("Cypher.query failed: "+query, e);
		}
    }
}

I hope this helps. I'm certainly no expert in this area, but if doing things this way is resulting in an error for you, it'll be interesting to see the error message and minimum sample code that produces that error. And perhaps I or someone else could help!

1 Like

That's pretty slick, and much cleaner than what I'm doing!

Thank you!