Triggers
In a trigger you register Cypher statements that are called when data in Neo4j is changed (created, updated, deleted). You can run them before or after commit.
Enable apoc.trigger.enabled=true
in $NEO4J_HOME/conf/apoc.conf
first.
|
add a trigger statement under a name,
in the statement you can use |
|
remove previously added trigger, returns trigger information |
|
removes all previously added triggers , returns trigger information |
|
update and list all installed triggers |
|
it pauses the trigger |
|
it resumes the paused trigger |
The transaction data from Neo4j is turned into appropriate data structures to be consumed as parameters to your statement, i.e. $createdNodes
.
The parameters available are:
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 deleted our trigger fires (list of nodes) |
deletedRelationships |
when a relationship is deleted 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) |
metaData |
a map containing the metadata of that transaction. Transaction meta data can be set on client side e.g. via https://neo4j.com/docs/api/java-driver/current/org/neo4j/driver/TransactionConfig.html#metadata-- |
You can use these helper functions to extract nodes or relationships by label/relationship-type or updated property key.
|
function to filter entries by label, to be used within a trigger statement with |
|
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
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
CREATE (d:Person {name:'Daniel', surname: 'Craig'})
CREATE (l:Person {name:'Mary', age: 47})
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)
With the above dataset, if we add a trigger and we execute for example, MATCH (n:Person) WHERE n.name IN ['Daniel', 'Mary'] SET n.age=55, n.surname='Quinn'
,
the $assignedNodeProperties
which can be used in the trigger statement,
will be as follows (where NODE(1)
is (:Person {name: 'Daniel'})
, and NODE(2) is (:Person {name: 'Mary'})
):
{
age: [{
node : NODE(1),
new: 55,
old: null,
key: "age"
},
{
node: NODE(2),
new: 55,
old: 47,
key: "age"
}],
surname: [{
node: NODE(1),
new: "Quinn",
old: "Craig",
key: "surname"
},
{
node: NODE(2),
new: "Quinn",
old: null,
key: "surname"
}]
}
As we can see, the result is a map of list, where the keys are the assigned properties, and the values are a list of entities involved.
Every element of a list have the node itself, the new value of the changed properties, the old value (or null
if the property didn’t exist) and the key with the property name.
The $removedNodeProperties
parameter has the same structure and logic (of course, in this case new
values will be always null
).
Same thing regarding assignedRelationshipProperties
and removedRelationshipProperties
,
with the only difference that instead of node: NODE(n)
key, we’ll have relationship: RELATIONSHIP(n)
.
For example, if we want to create a trigger that at every set, update 2 property "time" and "lasts" with the current date and the property updated, we can do:
CALL apoc.trigger.add('setLastUpdate',
'UNWIND keys($assignedNodeProperties) as k
UNWIND $assignedNodeProperties[k] AS map
with map.node as node, collect(map.key) as propList
match (n) where id(n) = id(node)
where not "lasts" in propList // to prevent loops
SET n.time = date(), n.lasts = propList', {phase:'afterAsync'});
In the example above, we put match (n) where id(n) = id(node)
to demonstrate that the we pull the node by id into parameters.
Anyway, we can get rid off this one and change last row with SET node.time = date(), node.lasts = propList
.
Note that we have to put the clause where not "lasts" in propList
to prevent infinite cascade SET.
Then, we can execute:
MATCH (n:Person {name: 'Daniel'}) set n.age = 123, n.country = 'Italy'
Executing
MATCH (n:Person {name: 'Daniel'}) return n
we can see the property time
with the today’s date, and lasts=['country','age']
.
So when we add the surname
property on a node, it’s added to all the nodes connected (in this case one level deep)
MATCH (d:Person {name:'Daniel'})
SET d.surname = 'William'
Now we add the trigger using apoc.trigger.propertiesByKey
on the surname
property
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)
MATCH (d:Person {name:'Daniel'})
SET d.surname = 'William'
The surname
property is add/change on all related nodes
Dataset
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)
We add a trigger using apoc.trigger.nodesByLabel
that when the label Actor
of a node is removed, update all labels Actor
with Person
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'})
MATCH(k:Actor {name:'Keanu Reeves'})
REMOVE k:Actor
We can add a trigger that connect every new node with label Actor
and as name
property a specific value
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'})
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'})
Generally, is recommended to use afterAsync
phase, to prevent some annoying transaction locks.
For example, given this trigger:
CALL apoc.trigger.add('lockTriggerTest1','UNWIND apoc.trigger.propertiesByKey($assignedNodeProperties,"name") as prop
WITH prop.node as n
CREATE (z:AnotherNode {myId: id(n)})
CREATE (n)-[:GENERATED]->(z)',
{phase:'after'});
if we execute:
MATCH (n:Person {name: 'John'}) set n.name = 'Jack'
the query will remain pending indefinitely.
To solve this, we can use {phase:'afterAsync'}
We have the possibility to pause a trigger without remove it, if we will need it in the future
When you need again of a trigger paused
For this example, we would like that all the reference
node properties are of type STRING
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'})
CREATE (a:Node) SET a.reference = 1
Neo.ClientError.Transaction.TransactionHookFailed
We can pass as a 4th parameter, a {params: {parameterMaps}}
to insert additional parameters.
CALL apoc.trigger.add('timeParams','UNWIND $createdNodes AS n SET n.time = $time', {}, {params: {time: timestamp()}});
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)', {})
|
Description |
|
The trigger will be activate right |
|
The trigger will be activate right after the |
|
The trigger will be activate right |
|
The trigger will be activate right |
Export metadata
To import triggers in another database (for example after a |