Execution failed, transaction closed

Hi!
I have written quite a bit of stored procedures for neo 3.5, so i started looking at porting it to 4.0.
So Ive tried to make a very simple function to merge a node, just to try out, and then return the node.
If I dont return the node, it works fine, but i really dont understand what is wrong with the code. The unit test i wrote works. Any help/explanation would be very helpful!

To return the node I have stolen the NodeResult class from apoc:
import org.neo4j.graphdb.Node;

public class NodeResult {
public final Node node;

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

@Override
public boolean equals(final Object o) {
    return this == o || o != null && getClass() == o.getClass() && node.equals(((NodeResult) o).node);
}

@Override
public int hashCode() {
    return node.hashCode();
}

}

and this is my very simple merge function:

package test;

import org.neo4j.graphdb.;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.logging.Log;
import org.neo4j.procedure.
;

import java.util.stream.Stream;
import test.NodeResult;

public class Insert {
// This field declares that we need a GraphDatabaseService
// as context when any procedure in this class is invoked
@Context
public GraphDatabaseService db;

@Context
public Log log;

@Procedure(name = "test.merge", mode = Mode.WRITE)
@Description("CALL test.merge(label, id)")
public Stream<NodeResult> merge(@Name(value = "label") String label, 
                                @Name(value = "id") Long id){
    Node node;
    try (Transaction tx = db.beginTx()){
        Label l = Label.label(label);
        
        node = tx.findNode(l, "id", id);
        
        if(node == null){
        
            node = tx.createNode();
            node.addLabel(l);
        }
        tx.commit();
        return Stream.of(new NodeResult(node));
    }
    
}

}

Thanks!

I'm pretty sure you're doing something with the returned node after the transaction has closed. This is an important change in 4.0: each node/relationship instance is bound to the transaction that it originates from.

The suggested way to deal with it is:

   Node node =  .... some dome from a transaction
   try (Transaction tx = db.beginTx() {
      node = tx.getNodeById(node.getId()); // this "rebinds" the node
     tx.commit();
   }

I got it to work, after looking at the apoc code.
What I did was to change the @Context to be a public Transaction tx instead of a GraphDatabaseService, and using that in my function instead.
The java doc doesnt mention Transaction to be a supported resource by the way
( https://neo4j.com/docs/java-reference/4.0/javadocs/org/neo4j/procedure/Procedure.html).

I guess that this transaction is created when I invoke the "call test.insert(blah, blah)" cypher command, and that a commit is done automatically when the cypher statement ends?

And that if I use a GraphDatabaseService as context instead of a transaction, I cant return graph entities, as whatever transaction I begin in my code will close when I return, making the entities inaccessible.
Or I could use a GraphDatabaseService and a Context Transaction, do stuff in a transaction created by the GraphDatabaseService, and then rebind them to the supplied Transaction before returning.
Have I understood things correctly? :)

1 Like

Correct, the @Context Transaction is the context from the cypher statement containing your procedure invocation.
Your idea of rebind would work, but the question is why you need another transaction?
In APOC e.g. apoc.periodic.iterate needs to create new transactions for the sake of batching. That's a valid use case.

Don't think I would need one, just trying to understand stuff :)
Do believe it would be helpful if the Transaction context was mentioned in the Procedure documentation though.

Love neo4j by the way.. keep up the good work!