I'm trying to figure out if there's any way that I can access the data about what nodes/relationships have been modified in the current transaction so far, then run some application validation code based on that data, and then decide whether to commit or rollback the transaction. Is that possible?
I know that a before phase trigger has the information I need (via createdRelationships, assignedNodeProperties, etc.), but there seems to be no way to run application code between this trigger happening and the actual commit of the transaction. (I even tried throwing an exception in the trigger, thinking I could try to commit, put the data I want into the transaction metadata within the trigger, throw an exception in the trigger, then catch the exception in my application, update the transaction based on the metadata, and then try the commit [and trigger] again, but it seems that if the trigger throws an exception then the whole transaction is closed and one cannot try to commit again.)
I looked into a custom procedure, because something like CALL my.procedures.getCurrentTransactionChanges() yield createdRelationships, ... would work perfectly, but from what I can tell, the information I need is only readily available via ReadableTransactionState and to get that I'd need KernelTransactionImplementation.txState(). I can see that this system procedure has a way of getting the KernelTransactionImplementation, but from reading the procedure context docs it seems clear that this would be an unsupported/private API and subject to change at any time.
Is there a better way to get this information?
The use case is: I want to allow application code to use cypher to make arbitrary changes to the database, but then before committing those changes I want to validate properties (e.g. username must be lowercase, that sort of thing), enforce business logic like ensuring certain relationships are 1:1, etc. It's a TypeScript application so I want the validation to happen in application code for various reasons; I know I could write triggers to do limited validation in Cypher but it wouldn't be as effective. If this is a terrible idea for some reason, I'd also like to know.
When you throw an exception in the before commit the transaction is already marked for rollback so you cannot execute commit on the same transaction.
Also, you can modify the content in the beforeCommit section. From the description provided it might be bit too much to be able to do in Cypher using apoc triggers.
You can implement your own transaction handler by looking at apoc trigger code and implement beforeCommit method.
In that method you get access to nodes, relationships and properties. If you delete a node or relationship or update property value there the updated content is the one that gets committed.
Say for example you have
CREATE (n:TestNode)
CREATE (n1:TestNode)
you will get that node object in your beforeCommit hook. if you delete one node in your hook only one would be written to db.
Remember there won't be any node id's associated to that node yet as it is not committed.
Thanks @anthapu. I'm not sure that really helps me though because I'm wondering if I can run my Node.js/TypeScript code during beforeCommit. (Or get access to the transaction change data before trying to commit). I know that I can run Cypher code and Java code in beforeCommit, but what I can't figure out how to do is to get my application, in this case a Node.js app, to review the transaction changes and make changes to the transaction before proceeding with the commit.
I want my Node.js code to look like this:
await dbWrite(async (tx) => {
// Make a bunch of changes to the database, within transaction tx
await tx.run('MATCH, MERGE, ...');
await thirdPartyCode.doStuff(tx);
// See what changes were made by the above code:
const changeList = await tx.run(`CALL myProc.getTransactionChanges yield createdRelationships, assignedNodeProperties`);
// Make some more changes to the transaction tx
await tx.run('MATCH, MERGE, ...');
tx.commit(); // Now commit
});
But from what I can tell, it may not be possible to write such a custom procedure without relying on Neo4j's private API.