Apoc triggers to get the data of updated relationship

I am trying to set up triggers in my db. Triggers for relationship creation, update and deletion. I have been able to set triggers for relationship creation properly.

CALL apoc.trigger.install(
  'neo4j', 
  'POST_Created_Relationships',
  '
    UNWIND $createdRelationships AS r
    WITH {
      id: id(r),
      type: type(r),
      properties: properties(r),
      startNodeId: id(startNode(r)),
      endNodeId: id(endNode(r))
    } AS relData
    CALL apoc.load.jsonParams(
      "http://192.168.1.122:9500/api/trigger/relationCreatedTrigger", 
      {method: "POST"}, 
      apoc.convert.toJson(relData)
    ) YIELD value
    RETURN value
  ', 
  {}
)

the above trigger statement hits the external api and provides the data of the created relationship.

for example, i created a relationship using this command

MATCH (p:Product) , (b:Brand) CREATE (p)-[rel:NEWRELN {additionalProp:"additionalpropvalue",anotherField:"anotherval"}]->(b);

here's the response from the trigger

'{"startNodeId":149,"id":40,"type":"NEWRELN","endNodeId":148,"properties":{"anotherField":"anotherval","additionalProp":"additionalpropvalue"}}': ''

this is not a proper response as all the data are present in key, but still i can extract the data.

i want to achieve the same for relationship updates (when any property of a relation is changed)

here's my trigger

CALL apoc.trigger.install(
   'neo4j', 
   'POST_Updated_Relationships',
   '
     UNWIND $assignedRelationshipProperties AS r
     CALL apoc.load.jsonParams("http://192.168.1.122:9500/api/trigger/relationUpdatedTrigger", {method: "POST"}, apoc.convert.toJson({data: r})) YIELD value
     RETURN value
   ', 
   {}
)

after putting this trigger i just updated both the values of the relationship with this command

MATCH  (p:Product)-[rel:NEWRELN]->(b:Brand) SET rel.additionalProp="updatedprop", rel.anotherField="updatedval";

this triggr is sending data in this format (ignore the slashes)

  "{\"data\":{\"anotherField\":[{\"new\":\"updatedval\",\"old\":\"anotherval\",\"relationship\":{\"id\":\"40\",\"type\":\"relationship\",\"label\":\"NEWRELN\",\"start\":{\"id\":\"149\",\"type\":\"node\",\"labels\":": {
    "\"Product\"": {
      "\"Brand\"": {
        "\"Product\"": {
          "\"Brand\"": ""
        }
      }
    }
  }

here in this trigger response i am getting only one of the values of the field which i updated, no matter how many fields i am updating in this trigger's response i am only getting only one updated property. how can i get the entire data of this relationship, just like i am getting for the createdRelationship case.

i tried following the similar approach as of createdRelationships

     WITH {
       id: id(r),
       type: type(r),
       properties: properties(r),
       startNodeId: id(startNode(r)),
       endNodeId: id(endNode(r))
     } AS relData

but this is not working as there is difference in the format how $createdRelationships is capturing the data and how $assignedRelationshipProperties is capturing the data.

Something looks really off. What is your API doing? What testing have you done.

my use-case is: there are several nodes and relationships in my application, the properties of the nodes and relationships in the db will keep changing though an another service, now as soon as there is any change in any property of a node or relationship i have to send a notification mentioning that there has been changes in this particular node/relation and now this node/relation has these values.
For this purpose i have an local server running which has different api endpoints for nodeUpdates and realtionshipUpdates, as soon as a property of node is changing my neo4j db hits that API and send the updated data of the node as request body. As mentioned above, I have been successful with nodes update, i have been able to implement exactly as per my requirements (please refer above, i have mentioned in detail) i want to achieve similar for relationship updates.
In simple words i want to know what does $assignedRelationshipProperties contain and can i extract the data of the updated relationship from this property.
Is it similar to $assignedNodeProperties, because in case of nodes i am able to extract the entire data of that particular node which got updated whereas i am unable to do same with $assignedRelationshipProperties

I understand what you want. I just thought something was off, based on what you were showing from your API result. I did a little testing and was able to reproduce the result you are getting with the create nodes, i.e. the payload shown as the key and empty string as the value.

What I believe is happening here is that the apoc.load.jsonParams procedure is making the post with content-type = application/x-www-form-urlencoded and your API is not parsing it correctly.

I am testing with a springboot application. When I tried to use @ResquestBody to have Jackson parse it, I got the following error (which gave me the clue):

[org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'application/x-www-form-urlencoded;charset=UTF-8' is not supported

I tried setting a Content-type header in the jsonParams method to override this, but it did not like the hyphen.

My workaround was to avoid having springboot use Jackson to parse the request body. Instead, I got the HttpServletRequest in the controller and parsed its InputStream myself. This resulted in a proper json representation of the payload sent by the apoc.load.jsonParams procedure. The following is what was sent by the apoc.load.jsonParams procedure and logged by my API.

{"startNodeId":295,"id":208,"type":"NEWRELN","endNodeId":296,"properties":{"anotherField":"anotherval","additionalProp":"additionalpropvalue"}}

Next I installed your trigger to get the property updates for relationships. I just sent the relationship as is in the trigger to see what was being sent. The following is what was sent and logged in my API:

{"anotherField":[{"new":"new_anotherval","old":"anotherval","relationship":{"id":"159","type":"relationship","label":"NEWRELN","start":{"id":"197","type":"node","labels":["Product"]},"end":{"id":"198","type":"node","labels":["Brand"]},"properties":{"anotherField":"new_anotherval","additionalProp":"new_additionalpropvalue"}},"key":"anotherField"}],"additionalProp":[{"new":"new_additionalpropvalue","old":"additionalpropvalue","relationship":{"id":"159","type":"relationship","label":"NEWRELN","start":{"id":"197","type":"node","labels":["Product"]},"end":{"id":"198","type":"node","labels":["Brand"]},"properties":{"anotherField":"new_anotherval","additionalProp":"new_additionalpropvalue"}},"key":"additionalProp"}]}

The trigger's cypher was the following:

UNWIND $assignedRelationshipProperties AS r
    CALL apoc.load.jsonParams(
      "http://localhost", 
      {method: "POST"}, 
      apoc.convert.toJson(r)
    ) YIELD value
     RETURN value

In conclusion, I think your API may not be correctly parsing what is being sent from the trigger; thus, giving you some weird results.

I was able to override the content type with a header parameter. Now my REST API deserializes the payload correctly into a Map<String, Object> using Jackson. You can deserialize into specific objects if you do define some for each payload. I did not because I wanted to experiment.

The fix is to include the following entry in the header map:

`Content-Type`: "application/json"

Here are the triggers that I got working:

Creating Nodes:

CALL apoc.trigger.install(
  'neo4j', 
  'POST_Created_Nodes',
  '
    UNWIND $createdNodes AS n
    CALL apoc.load.jsonParams(
      "http://localhost",
      {
        method: "POST",
        `Content-Type`: "application/json"
      },
      apoc.convert.toJson(n)
    ) yield value
    RETURN value
', 
  {}
)

Creating relationships:

CALL apoc.trigger.install(
  'neo4j', 
  'POST_Created_Relationships',
  '
    UNWIND $createdRelationships AS r
    CALL apoc.load.jsonParams(
      "http://localhost",
      {
        method: "POST",
        `Content-Type`: "application/json"
      },
      apoc.convert.toJson(r)
    ) yield value
    RETURN value
', 
  {}
)

Updating relationships:

CALL apoc.trigger.install(
  'neo4j', 
  'POST_Updated_Relationships',
  '
    UNWIND $assignedRelationshipProperties AS n
    CALL apoc.load.jsonParams(
      "http://localhost",
      {
        method: "POST",
        `Content-Type`: "application/json"
      },
      apoc.convert.toJson(n)
    ) yield value
    RETURN value
', 
  {}
)

the logs for each test are the following:

payload: {id=329, type=node, labels=[XX], properties={id=0}}
payload: {id=225, type=relationship, label=REL, start={id=329, type=node, labels=[XX], properties={id=0}}, end={id=329, type=node, labels=[XX], properties={id=0}}}
payload: {new_attribute=[{new=100, old=null, relationship={id=225, type=relationship, label=REL, start={id=329, type=node, labels=[XX], properties={id=0}}, end={id=329, type=node, labels=[XX], properties={id=0}}, properties={new_attribute=100}}, key=new_attribute}]}

Thanks a lot @glilienfield this worked.

Now i have to track the deleted relationships in similar way, i need to get the id or some other identifier of the deleted relationship and send it to the api endpoint.

here's my trigger for the deleted relationships

CALL apoc.trigger.install(
   'neo4j', 
   'POST_Deleted_Relationships',
   '
     UNWIND $deletedRelationships AS n
     CALL apoc.load.jsonParams(
       "http://192.168.1.122:9500/api/trigger/relationDeletedTrigger", 
       {
        method: "POST",
        `Content-Type`: "application/json"
        }, 
       apoc.convert.toJson(n)
     ) YIELD value
     RETURN value
   ', 
   {}
)

The issue here is after setting this trigger as soon as i am deleting a relationship, the DB is giving error. Though, the api endpoint which is provided in the trigger that api is being called but empty object is being sent as req body.

Error executing triggers {POST_Deleted_Relationships=Failed to invoke function `apoc.convert.toJson`: Caused by: org.neo4j.internal.kernel.api.exceptions.EntityNotFoundException: Unable to load RELATIONSHIP 5:bde2dc64-abfb-4302-b727-3bfc3711d947:20., POST_Created_Nodes=The transaction has been terminated. Retry your operation in a new transaction, and you should see a successful result. Failed to invoke a procedure. See the detailed error description for exact cause. }

Now in this error message the ID is being shown "5:bde2dc64-abfb-4302-b727-3bfc3711d947:20" . is there any way to capture this id and send it to api endpoint.

Commands i used for creating and deleting a relationship:

CREATE (n:Person {name:"john", age:20}) 

CREATE (n:Person {name:"jack", age:22}); 

MATCH (a:Person {name:"john"}), (b:Person {name:"jack"}) CREATE (a)-[rel:Friend {since:2020}]->(b) RETURN rel;

MATCH (a:Person {name:"john"})-[rel:Friend]->(b:Person {name:"jack"}) DELETE rel;

It looks like the relationship is no longer available to pass to the toJson method. I was able to extract the 'id', 'elementId', and 'type' of the relationship when sending to the toJson method. I was not able to get the 'properties' for the relationship.

This worked:

CALL apoc.trigger.install(
   'neo4j', 
   'POST_delete_relationships',
   '
     UNWIND $deletedRelationships AS n
     CALL apoc.load.jsonParams(
       "http://localhost:9500/api/trigger/relationDeletedTrigger", 
       {
        method: "POST",
        `Content-Type`: "application/json"
        }, 
       apoc.convert.toJson({id: id(n), elementId: elementId(n), type: type(n)})
     ) YIELD value
     RETURN value
   ', 
   {}
)

is there any possibility that, before deletion of a relation/node actually happens, some property of that node/relation can be stored temporarily and then can be sent to the trigger's associated API's request body

I tried changing the phase of the trigger to 'before'. I got the same error about the transaction has been closed. As such, I don't think the trigger makes the properties available.