In a trigger you register Cypher statements that are called when data in Neo4j is changed, you can run them before or after commit.


Enable `apoc.trigger.enabled=true` in `$NEO4J_HOME/config/neo4j.conf` first.

[cols="1m,5"]
|===
| CALL apoc.trigger.add(name, statement, selector) yield name, statement, installed | add a trigger statement under a name, in the statement you can use {createdNodes}, {deletedNodes} etc., the selector is {phase:'before/after/rollback'} returns previous and new trigger information
| CALL apoc.trigger.remove(name) yield name, statement, installed | remove previously added trigger, returns trigger information
| CALL apoc.trigger.list() yield name, statement, installed | update and list all installed triggers
| CALL apoc.trigger.pause(name) | it pauses the trigger
| CALL apoc.trigger.resume(name) | it resumes the paused trigger
|===

The transaction data from Neo4j is turned into appropriate data structures to be consumed as parameters to your statement.

The parameters available are:

[options="header"]
|===
|Statement | Description
|transactionId | returns the id of the transaction
|commitTime | return the date of the transaction in milliseconds
|createdNodes | when a node is created our trigger fires (list of nodes)
|createdRelationships | when a relationship is created our trigger fires (list of relationships)
|deletedNodes | when a node is delated our trigger fires (list of nodes)
|deletedRelationships | when a relationship is delated our trigger fires (list of relationships)
|removedLabels | when a label is removed our trigger fires (map of label to list of nodes)
|removedNodeProperties | when a properties of node is removed our trigger fires (map of key to list of map of key,old,node)
|removedRelationshipProperties | when a properties of relationship is removed our trigger fires (map of key to list of map of key,old,relationship)
|assignedLabels | when a labes is assigned our trigger fires  (map of label to list of nodes)
|assignedNodeProperties | when node property is assigned our trigger fires (map of key to list of map of key,old,new,node)
|assignedRelationshipProperties | when relationship property is assigned our trigger fires (map of key to list of map of key,old,new,relationship)
|===

You can use these helper functions to extract nodes by label or

.Helper Functions
[cols="1m,5"]
|===
| apoc.trigger.nodesByLabel({assignedLabels/assignedNodeProperties},'Label') | function to filter labelEntries by label, to be used within a trigger statement with {assignedLabels} and {removedLabels} {phase:'before/after/rollback'} returns previous and new trigger information
| apoc.trigger.propertiesByKey({assignedNodeProperties},'key') | function to filter propertyEntries by property-key, to be used within a trigger statement with {assignedNode/RelationshipProperties} and {removedNode/RelationshipProperties}. Returns [{old,[new],key,node,relationship}]
|===

=== Triggers Examples

.Set properties connected to a node

We could add a trigger that when is added a specific property on a node, that property is added to all the nodes connected to this node

Dataset

[source,cypher]
----
CREATE (d:Person {name:'Daniel'})
CREATE (l:Person {name:'Mary'})
CREATE (t:Person {name:'Tom'})
CREATE (j:Person {name:'John'})
CREATE (m:Person {name:'Michael'})
CREATE (a:Person {name:'Anne'})
CREATE (l)-[:DAUGHTER_OF]->(d)
CREATE (t)-[:SON_OF]->(d)
CREATE (t)-[:BROTHER]->(j)
CREATE (a)-[:WIFE_OF]->(d)
CREATE (d)-[:SON_OF]->(m)
CREATE (j)-[:SON_OF]->(d)
----

image::{img}/apoc.trigger.add.setAllConnectedNodes.dataset.png[width=800]

Now we add the trigger using `apoc.trigger.propertiesByKey` on the `surname` property

[source,cypher]
----
CALL apoc.trigger.add('setAllConnectedNodes','UNWIND apoc.trigger.propertiesByKey({assignedNodeProperties},"surname") as prop
WITH prop.node as n
MATCH(n)-[]-(a)
SET a.surname = n.surname', {phase:'after'});
----

So when we add the `surname` property on a node, it's added to all the nodes connected (in this case one level deep)

[source,cypher]
----
MATCH (d:Person {name:'Daniel'})
SET d.surname = 'William'
----

image::{img}/apoc.trigger.add.setAllConnectedNodes.png[width=800]

The `surname` property is add/change on all related nodes

.Update labels

Dataset

[source,cypher]
----
CREATE (k:Actor {name:'Keanu Reeves'})
CREATE (l:Actor {name:'Laurence Fishburne'})
CREATE (c:Actor {name:'Carrie-Anne Moss'})
CREATE (m:Movie {title:'Matrix'})
CREATE (k)-[:ACT_IN]->(m)
CREATE (l)-[:ACT_IN]->(m)
CREATE (c)-[:ACT_IN]->(m)
----

image::{img}/apoc.trigger.add.setLabels.png[width=800]

We add a trigger using `apoc.trigger.nodesByLabel` that when the label `Actor` of a node is removed, update all labels `Actor` with `Person`

[source,cypher]
----
CALL apoc.trigger.add('updateLabels',"UNWIND apoc.trigger.nodesByLabel({removedLabels},'Actor') AS node
MATCH (n:Actor)
REMOVE n:Actor SET n:Person SET node:Person", {phase:'before'})
----

[source,cypher]
----

MATCH(k:Actor {name:'Keanu Reeves'})
REMOVE k:Actor
----

image::{img}/apoc.trigger.add.setLabelsResult.png[width=800]

.Create relationship on a new node

We can add a trigger that connect every new node with label `Actor` and as  `name` property a specific value

[source,cypher]
----
CALL apoc.trigger.add('create-rel-new-node',"UNWIND {createdNodes} AS n
MATCH (m:Movie {title:'Matrix'})
WHERE n:Actor AND n.name IN ['Keanu Reeves','Laurence Fishburne','Carrie-Anne Moss']
CREATE (n)-[:ACT_IN]->(m)", {phase:'before'})
----

[source,cypher]
----
CREATE (k:Actor {name:'Keanu Reeves'})
CREATE (l:Actor {name:'Laurence Fishburne'})
CREATE (c:Actor {name:'Carrie-Anne Moss'})
CREATE (a:Actor {name:'Tom Hanks'})
CREATE (m:Movie {title:'Matrix'})
----

image::{img}/apoc.trigger.add.create-rel-new-node.png[width=800]

.Pause trigger

We have the possibility to pause a trigger without remove it, if we will need it in the future

image::{img}/apoc.trigger.pause.png[width=800]


.Resume paused trigger

When you need again of a trigger paused

image::{img}/apoc.trigger.resume.png[width=800]

.Enforcing property type

For this example, we would like that all the `reference` node properties are of type `STRING`

[source,cypher]
----
CALL apoc.trigger.add("forceStringType",
"UNWIND apoc.trigger.propertiesByKey({assignedNodeProperties}, 'reference') AS prop
CALL apoc.util.validate(apoc.meta.type(prop) <> 'STRING', 'expected string property type, got %s', [apoc.meta.type(prop)]) RETURN null", {phase:'before'})
----

[source,cypher]
----
CREATE (a:Node) SET a.reference = 1

Neo.ClientError.Transaction.TransactionHookFailed
----

.Other examples
[source,cypher]
----
CALL apoc.trigger.add('timestamp','UNWIND {createdNodes} AS n SET n.ts = timestamp()');
CALL apoc.trigger.add('lowercase','UNWIND {createdNodes} AS n SET n.id = toLower(n.name)');
CALL apoc.trigger.add('txInfo',   'UNWIND {createdNodes} AS n SET n.txId = {transactionId}, n.txTime = {commitTime}', {phase:'after'});
CALL apoc.trigger.add('count-removed-rels','MATCH (c:Counter) SET c.count = c.count + size([r IN {deletedRelationships} WHERE type(r) = "X"])')
CALL apoc.trigger.add('lowercase-by-label','UNWIND apoc.trigger.nodesByLabel({assignedLabels},'Person') AS n SET n.id = toLower(n.name)')
----

// end::trigger[]
