Triggers
Triggers allow the registration of Cypher queries that are called when data in Neo4j is changed (created, updated, deleted). Triggers can be run before or after a commit.
The There are several ways of doing this:
Moreover, the |
Installing, updating, or removing a trigger is an eventually consistent operation.
Therefore, they are not immediately added/updated/removed,
but have a refresh rate handled by the APOC configuration |
By default triggers are disabled.
We can enable them by setting the following property in apoc.conf
:
apoc.trigger.enabled=true
apoc.trigger.refresh=60000
Option Key | Value | Description |
---|---|---|
apoc.trigger.enabled |
true/false, default false |
Enable/Disable the feature |
apoc.trigger.refresh |
number, default 60000 |
Interval in ms after which a replication check is triggered across all cluster nodes |
Qualified Name | Type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The transaction data from Neo4j is turned into appropriate data structures to be consumed as parameters to a statement, i.e. $createdNodes
.
The parameters available are:
Statement | Description |
---|---|
|
returns the id of the transaction |
|
returns the date of the transaction in milliseconds |
|
when a node is created our trigger fires (list of nodes) |
|
when a relationship is created our trigger fires (list of relationships) |
|
when a node is deleted our trigger fires (list of nodes) |
|
when a relationship is deleted our trigger fires (list of relationships) |
|
when a label is removed our trigger fires (map of label to list of nodes) |
|
when a properties of node is removed our trigger fires (map of key to list of map of key,old,node) |
|
when a properties of relationship is removed our trigger fires (map of key to list of map of key,old,relationship) |
|
when a labes is assigned our trigger fires (map of label to list of nodes) |
|
when node property is assigned our trigger fires (map of key to list of map of key,old,new,node) |
|
when relationship property is assigned our trigger fires (map of key to list of map of key,old,new,relationship) |
|
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/org/neo4j/driver/TransactionConfig.html#metadata() |
The third parameter of the apoc.trigger.install()
is a map {phase: PHASE}
, where PHASE
is a string that can have one of the following values:
|
Description |
|
The trigger will be activated |
|
The trigger will be activated right after the |
|
The trigger will be activated |
|
The trigger will be activated |
Unlike previous versions of Neo4j, it will not be possible using Neo4j 5 to modify an entity created in phase: “after”.
For example, the following query will return an Exception with message
It is instead necessary to use another phase or perform only reading operations. |
Triggers Examples
It is possible to add a trigger which, when added to a specific property on a node, adds the same property to all nodes connected to this node.
Dataset (in default database 'neo4j')
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 a trigger is added and the following query is executed: MATCH (n:Person) WHERE n.name IN ['Daniel', 'Mary'] SET n.age=55, n.surname='Quinn'
, the $assignedNodeProperties
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"
}]
}
The result is a map 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 (in this case, new
values will be always null
).
The same is true for assignedRelationshipProperties
and removedRelationshipProperties
, with the only difference being that the node: NODE(n)
key is replaced with the relationship: RELATIONSHIP(n)
key.
As an example, the following statement creates a trigger which for every SET
, updates the two properties time
and lasts
with the current date:
CALL apoc.trigger.install('neo4j', '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) AND NOT 'lasts' in propList // to prevent loops
SET n.time = date(), n.lasts = propList",
{phase: 'afterAsync'});
Note that the apoc.trigger.install
, as well as the apoc.trigger.drop
, apoc.trigger.dropAll
, apoc.trigger.stop
and apoc.trigger.start
,
have to be executed in the system database.
In the example above, MATCH (n) WHERE id(n) = id(node)
is used to demonstrate that the node is found by id first, before setting its parameters.
However, it is more efficient to remove this command and instead change the penultimate row to: SET node.time = date(), node.lasts = propList
.
Note that the condition AND NOT 'lasts' IN propList
must be added to prevent an infinite loop as the SET
command will trigger this query again.
It is then possible to execute the following query, after a time defined by the configuration apoc.trigger.refresh
:
MATCH (n:Person {name: 'Daniel'}) set n.age = 123, n.country = 'Italy'
Executing
MATCH (n:Person {name: 'Daniel'}) return n
It is possible to set the property time
with today’s date, and lasts=['country','age']
.
In cases where the surname
property is added to a node, it’s added to all the nodes connected to it as well (in this case one level deep).
MATCH (d:Person {name:'Daniel'})
SET d.surname = 'William'
To add a trigger that connects every new node with the label Actor
and assign a specific value to the name
property, run the following query:
CALL apoc.trigger.install('neo4j','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'})
To prevent certain transaction locks, it is generally recommended to use the afterAsync
phase.
This will stop the query from pending indefinitely.
Note that the apoc.trigger.stop
and the apoc.trigger.start
procedures are eventually consistent.
It is therefore necessary to wait a set amount of time for the changes to propagate.
The waiting time is defined by the configuration apoc.trigger.refresh
.
To pause a trigger without removing it for future purposes, use the following procedure:
To resume a paused trigger, use the following procedure:
Add \{params: {parameterMaps}}
to insert additional parameters.
CALL apoc.trigger.install('neo4j', 'timeParams','UNWIND $createdNodes AS n SET n.time = $time', {}, {params: {time: timestamp()}});
CALL apoc.trigger.install('neo4j', 'timestamp','UNWIND $createdNodes AS n SET n.ts = timestamp()', {});
CALL apoc.trigger.install('neo4j', 'lowercase','UNWIND $createdNodes AS n SET n.id = toLower(n.name)', {});
CALL apoc.trigger.install('neo4j', 'txInfo','UNWIND $createdNodes AS n SET n.txId = $transactionId, n.txTime = $commitTime', {phase:'after'});
CALL apoc.trigger.install('neo4j', 'count-removed-rels','MATCH (c:Counter) SET c.count = c.count + size([r IN $deletedRelationships WHERE type(r) = "X"])', {})
Remove triggers
To remove the trigger with name 'test' in 'neo4j' database, run the following query:
CALL apoc.trigger.drop('neo4j', 'test')
To remove all triggers in 'neo4j' db, run the following query:
CALL apoc.trigger.dropAll('neo4j')
List of triggers
It is possible to return the full list of triggers in a database. For example, if the trigger in the following query is created:
CALL apoc.trigger.install('neo4j', 'count-removals',
'MATCH (c:Counter) SET c.count = c.count + size([f IN $deletedNodes WHERE id(f) > 0])',
{})
It is then possible to run (also in this case, after a time defined by the configuration apoc.trigger.refresh
):
CALL apoc.trigger.show('neo4j')
name | query | selector | params | installed | paused |
---|---|---|---|---|---|
"count-removals" |
{} |
{} |
TRUE |
FALSE |
Please note that, since the trigger operations are eventually consistent (based on the |