Skip to content

Commit

Permalink
Fixes #4263: Adds docs for apoc triggers helper functions (#4311)
Browse files Browse the repository at this point in the history
* Revert "Removed org.apache.commons.collections from OpenAI (#4304)" (#4305)

This reverts commit 7e3bfb7.

* Fixes #4244: Make apoc.dv.* procedures work in clusters (#4281)

* Fixes #4244: Make apoc.dv.* procedures work in clusters

* added procs to extended*.txt

* fix tests

* cleanup

* Fixes #4263: Adds docs for apoc triggers helper functions

* Update apoc.trigger.toNode.adoc

* Update apoc.trigger.toRelationship.adoc
  • Loading branch information
vga91 authored Dec 19, 2024
1 parent de79ac3 commit dcd82f0
Show file tree
Hide file tree
Showing 20 changed files with 196 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
¦xref::overview/apoc.trigger/apoc.trigger.nodesByLabel.adoc[apoc.trigger.nodesByLabel icon:book[]] +

``
`apoc.trigger.nodesByLabel(labelEntries, label)` - function to filter labelEntries by label, to be used within a trigger kernelTransaction with `$assignedLabels`, `$removedLabels`, `$assigned/removedNodeProperties`.
¦label:function[]
¦label:apoc-extended[]
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
¦xref::overview/apoc.trigger/apoc.trigger.propertiesByKey.adoc[apoc.trigger.propertiesByKey icon:book[]] +

``
`apoc.trigger.propertiesByKey(propertyEntries, key)` - function to filter propertyEntries by property-key, to be used within a trigger kernelTransaction with `$assignedNode/RelationshipProperties` and `$removedNode/RelationshipProperties`. Returns [`old`,`new`,`key`,`node`,`relationship`].
¦label:function[]
¦label:apoc-extended[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
¦signature
¦apoc.trigger.nodesByLabel(labelEntries :: ANY?, label :: STRING?) :: (LIST? OF ANY?)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
¦xref::overview/apoc.trigger/apoc.trigger.toNode.adoc[apoc.trigger.toNode icon:book[]] +

`apoc.trigger.toNode(node, removedLabels, removedNodeProperties)` - function to rebuild a node as a virtual one, to be used in triggers with a not 'afterAsync' phase.
¦label:function[]
¦label:apoc-extended[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
¦type¦qualified name¦signature¦description
¦function¦apoc.trigger.nodesByLabel¦apoc.trigger.nodesByLabel(labelEntries :: ANY?, label :: STRING?) :: (LIST? OF ANY?)¦
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
¦signature
¦apoc.trigger.nodesByLabel(labelEntries :: ANY?, label :: STRING?) :: (LIST? OF ANY?)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
¦xref::overview/apoc.trigger/apoc.trigger.toRelationship.adoc[apoc.trigger.toRelationship icon:book[]] +

`apoc.trigger.toRelationship(rel, removedRelationshipProperties)` - function to rebuild a relationship as a virtual one, to be used in triggers with a not 'afterAsync' phase.
¦label:function[]
¦label:apoc-extended[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
¦type¦qualified name¦signature¦description
¦function¦apoc.trigger.nodesByLabel¦apoc.trigger.nodesByLabel(labelEntries :: ANY?, label :: STRING?) :: (LIST? OF ANY?)¦
1 change: 1 addition & 0 deletions docs/asciidoc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ include::partial$generated-documentation/nav.adoc[]
* xref:background-operations/index.adoc[]
** xref::background-operations/apoc-load-directory-async.adoc[]
** xref:background-operations/triggers.adoc[]
* xref:database-introspection/index.adoc[]
** xref::database-introspection/config.adoc[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@

It's possible to define listeners on one or more folders which trigger the executing of custom cypher queries if changes are observed:

* xref::background-operations/apoc-load-directory-async.adoc[]
* xref::background-operations/apoc-load-directory-async.adoc[]
* xref:background-operations/triggers.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
APOC Core provides a set of procedures for running Cypher queries that are called when data in Neo4j is changed (created, updated, deleted).

https://neo4j.com/docs/apoc/current/background-operations/triggers/[See here for more info].

In addition, APOC Extended provides some helper procedures to more easily manipulate Cypher queries and solve some transaction use cases that cannot be solved otherwise.

== Helper Functions

[separator=¦,opts=header,cols="5,1m,1m"]
|===
¦Qualified Name¦Type¦Release
include::example$generated-documentation/apoc.trigger.nodesByLabel.adoc[]
include::example$generated-documentation/apoc.trigger.propertiesByKey.adoc[]
include::example$generated-documentation/apoc.trigger.toNode.adoc[]
include::example$generated-documentation/apoc.trigger.toRelationship.adoc[]
|===
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

= apoc.trigger.toNode
:description: This section contains reference documentation for the apoc.trigger.toNode function.

label:function[] label:apoc-extended[]

== Signature

[source]
----
apoc.trigger.toNode(node :: NODE, removedLabels :: MAP, removedNodeProperties :: MAP) :: RELATIONSHIPH
----

== Input parameters
[.procedures, opts=header]
|===
| Name | Type | Default
|node|NODE|null
|removedLabels|MAP|null
|removedNodeProperties|MAP|null
|===

[[usage-apoc.trigger.nodesByLabel]]
== Usage Examples
include::partial$usage/apoc.trigger.toNode.adoc[]

xref::background-operations/triggers.adoc[More documentation of apoc.trigger.toNode,role=more information]

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

= apoc.trigger.toRelationship
:description: This section contains reference documentation for the apoc.trigger.toRelationship function.

label:function[] label:apoc-extended[]

== Signature

[source]
----
apoc.trigger.toRelationship(rel :: RELATIONSHIP, removedRelationshipProperties :: MAP) :: RELATIONSHIP
----

== Input parameters
[.procedures, opts=header]
|===
| Name | Type | Default
|rel|RELATIONSHIP|null
|removedRelationshipProperties|MAP|null
|===

[[usage-apoc.trigger.nodesByLabel]]
== Usage Examples
include::partial$usage/apoc.trigger.toRelationship.adoc[]

xref::background-operations/triggers.adoc[More documentation of apoc.trigger.toNode,role=more information]

17 changes: 0 additions & 17 deletions docs/asciidoc/modules/ROOT/partials/triggers.adoc

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ create constraint for (p:Person)
require p.id is unique;
----

This function is used inside an apoc.trigger.add Cypher statement.
This function is intended to be used inside an apoc.trigger.install Cypher statement.

We can use it to conditionally run Cypher statements when labels are added or removed or when properties are added or removed.
For example, we add an `id` property to all `Person` nodes that is the lower case value of the `name` property of that node, by defining the following trigger:

[source,cypher]
----
CALL apoc.trigger.add(
CALL apoc.trigger.install(
'neo4j',
'lowercase',
'UNWIND apoc.trigger.nodesByLabel($assignedLabels,"Person") AS n
SET n.id = toLower(n.name)',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
This function is used inside an apoc.trigger.add.adoc Cypher statement.
This function is intended to be used inside an apoc.trigger.install Cypher statement.

We can use it to conditionally run Cypher statements when properties are added or removed.
For example, we can connect nodes with a `genre` property to a `Genre` node, with the following trigger:

[source,cypher]
----
CALL apoc.trigger.add(
CALL apoc.trigger.install(
'neo4j',
'triggerTest',
'UNWIND apoc.trigger.propertiesByKey($assignedNodeProperties, "genre") as prop
WITH prop.node as n
Expand Down
46 changes: 46 additions & 0 deletions docs/asciidoc/modules/ROOT/partials/usage/apoc.trigger.toNode.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
This function is intended to be used within an `apoc.trigger.install` Cypher statement.

If we want to create a 'before' or 'after' trigger query using `$deletedNodes`, and retrieve entity information such as labels and/or properties, we cannot use the classic Cypher functions labels() and properties().
Instead, we have to leverage virtual nodes through the function `apoc.trigger.toNode(node, $removedLabels, $removedNodeProperties)`.

For example, to create a new `Report` node with a list of deleted node IDs and all the labels retrieved for each deleted node, we can execute:
[source,cypher]
----
CALL apoc.trigger.install(
'neo4j', 'myTrigger',
"UNWIND $deletedNodes as deletedNode
WITH apoc.trigger.toNode(deletedNode, $removedLabels, $removedNodeProperties) AS deletedNode
CALL apoc.merge.node(
['Report'],
{labels: apoc.node.labels(deletedNode)},
{created: datetime()},
{updated: datetime()}
) YIELD node AS report
WITH report, deletedNode
SET report.deletedIds = coalesce(report.deletedIds, [])+[id(deletedNode)]" ,
{phase:'before'}
);
----

Now, let's create and delete a `Movie` node:

[source,cypher]
----
CREATE (:Movie {title: "The White Tiger"});
MATCH (movie:Movie {title: "The White Tiger"}) DELETE movie;
----

Finally, let's check the `Report` node:

[source,cypher]
----
MATCH (report:Report {labels: ['Movie']})
RETURN report;
----

.Results
[opts="header"]
|===
| report
| (:Report {"created": "2024-12-12T08:33:27.188000000Z", "deletedIds": [12], "labels": ["Movie"]})
|===
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
This function is intended to be used within an `apoc.trigger.install` Cypher statement.

If we want to create a 'before' or 'after' trigger query using `$deletedRelationships`, and retrieve entity information such as the type and/or properties, we cannot use the classic Cypher functions type() and properties().
Instead, we have to leverage virtual relationships through the function `apoc.trigger.toRelationship(rel, $removedRelationshipProperties)`.

For example, to create a new `Report` node with a list of deleted relationship IDs and the type retrieved for each deleted relationship, we can execute:
[source,cypher]
----
CALL apoc.trigger.install(
'neo4j', 'myTrigger',
"UNWIND $deletedRelationships as deletedRel
WITH apoc.trigger.toRelationship(deletedRel, $removedRelationshipProperties) AS deletedRel
CALL apoc.merge.node(
['Report'],
{type: apoc.rel.type(deletedRel)},
{created: datetime()},
{updated: datetime()}
) YIELD node AS report
WITH report, deletedRel
SET report.deletedIds = coalesce(report.deletedIds, [])+[id(deletedRel)]" ,
{phase:'before'}
);
----

Now, let's create and delete a `IN_GENRE` relationship between a `Movie` node and a `Genre` node:

[source,cypher]
----
MERGE (movie:Movie {title: "The White Tiger"})
MERGE (genre:Genre {name: "Triller"})
MERGE (movie)-[IN_GENRE]->(genre);
MATCH (movie:Movie {title: "The White Tiger"})-[r:IN_GENRE]->(genre:Genre {name: "Triller"}) DELETE r;
----

Finally, let's check the `Report` node:

[source,cypher]
----
MATCH (report:Report {labels: ['IN_GENRE']})
RETURN report;
----

.Results
[opts="header"]
|===
| report
| (:Report {"created": "2024-12-12T08:33:27.188000000Z", "deletedIds": [12], "type": "IN_GENRE"})
|===
4 changes: 2 additions & 2 deletions extended/src/main/java/apoc/ml/OpenAI.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import apoc.ApocConfig;
import apoc.Extended;
import apoc.result.MapResult;
import apoc.util.ExtendedMapUtils;
import apoc.util.ExtendedUtil;
import apoc.util.JsonUtil;
import apoc.util.Util;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.neo4j.graphdb.security.URLAccessChecker;
import org.neo4j.procedure.Context;
Expand Down Expand Up @@ -225,7 +225,7 @@ public Stream<MapResult> completion(@Name("prompt") String prompt, @Name("api_ke
public Stream<MapResult> chatCompletion(@Name("messages") List<Map<String, Object>> messages, @Name("api_key") String apiKey, @Name(value = "configuration", defaultValue = "{}") Map<String, Object> configuration) throws Exception {
boolean failOnError = isFailOnError(configuration);
if (checkNullInput(messages, failOnError)) return Stream.empty();
messages = messages.stream().filter(ExtendedMapUtils::isNotEmpty).toList();
messages = messages.stream().filter(MapUtils::isNotEmpty).toList();
if (checkEmptyInput(messages, failOnError)) return Stream.empty();
configuration.putIfAbsent("model", GPT_4O_MODEL);
return executeRequest(apiKey, configuration, "chat/completions", (String) configuration.get("model"), "messages", messages, "$", apocConfig, urlAccessChecker)
Expand Down
4 changes: 0 additions & 4 deletions extended/src/main/java/apoc/util/ExtendedMapUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,4 @@ public static int size(final Map<?, ?> map) {
public static boolean isEmpty(final Map<?,?> map) {
return map == null || map.isEmpty();
}

public static boolean isNotEmpty(final Map<?,?> map) {
return !isEmpty(map);
}
}

0 comments on commit dcd82f0

Please sign in to comment.