Skip to content

Commit 717efc5

Browse files
authored
Fixes #4246: Add apoc.node.match and apoc.relationship.match procedures (#4277)
* Fixes #4246: Add apoc.node.match and apoc.relationship.match procedures * fix tests * fix tests * changed procedure naming * fix apache commons error and added docs with load json
1 parent 5da8113 commit 717efc5

File tree

11 files changed

+861
-127
lines changed

11 files changed

+861
-127
lines changed

docs/asciidoc/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ include::partial$generated-documentation/nav.adoc[]
9292
9393
* xref:misc/index.adoc[]
9494
** xref::misc/static-values.adoc[]
95+
** xref::misc/match-entities.adoc[]
9596
9697
* xref::transaction/index.adoc[]
9798

docs/asciidoc/modules/ROOT/pages/misc/index.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Cypher brings along some basic functions for math, text, collections and maps.
88

99
* xref::misc/static-values.adoc[]
10+
* xref::misc/match-entities.adoc[]
1011

1112

1213

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
[[match-entities]]
2+
= Match entities
3+
:description: This section describes procedures and functions for matching entities.
4+
5+
The library provides 2 procedure for matching entities:
6+
7+
- apoc.node.match
8+
- apoc.rel.match
9+
10+
[[matching-node]]
11+
== Matching nodes
12+
13+
[.emphasis]
14+
"apoc.node.match(['Label'], identProps:{key:value, ...}, onMatchProps:{key:value,...})" - match nodes with dynamic labels, with support for setting properties on matched nodes
15+
16+
=== Signature
17+
18+
[source]
19+
----
20+
apoc.node.match(label :: LIST? OF STRING?, identProps :: MAP?, onMatchProps = {} :: MAP?) :: (node :: NODE?)
21+
----
22+
23+
=== Input parameters
24+
[.procedures, opts=header]
25+
|===
26+
| Name | Type | Default | Description
27+
| labels | LIST? OF STRING? | null | The list of labels used for the generated MATCH statement. Passing `null` or an empty list will match a node without any constraints on labels. `null` or empty strings within the list are not supported.
28+
| identProps | MAP? | null | Properties that are used for MATCH statement.
29+
| onMatchProps | MAP? | {} | Properties that are set when a node is matched.
30+
|===
31+
32+
=== Output parameters
33+
[.procedures, opts=header]
34+
|===
35+
| Name | Type
36+
|node|NODE?
37+
|===
38+
39+
This procedure provides a more flexible and performant way of matching nodes than Cypher's https://neo4j.com/docs/cypher-manual/current/clauses/match/[`MATCH`^] clause.
40+
41+
=== Usage Examples
42+
The example below shows equivalent ways of matching a node with the `Person` label, with a `name` property of "Billy Reviewer":
43+
44+
// tag::tabs[]
45+
[.tabs]
46+
47+
.apoc.node.match
48+
[source,cypher]
49+
----
50+
CALL apoc.node.match(
51+
["Person"],
52+
{name: "Billy Reviewer"},
53+
{lastSeen: datetime()}
54+
)
55+
YIELD node
56+
RETURN node;
57+
----
58+
59+
.MATCH clause
60+
[source,cypher]
61+
----
62+
MATCH (node:Person {name: "Billy Reviewer"})
63+
SET node.lastSeen = datetime()
64+
RETURN node;
65+
----
66+
// end::tabs[]
67+
68+
.Results
69+
[opts="header"]
70+
|===
71+
| node
72+
| (:Person {name: "Billy Reviewer", lastSeen: 2020-11-24T11:33:39.319Z})
73+
|===
74+
75+
But this procedure is mostly useful for matching nodes that have dynamic labels or properties.
76+
For example, we might want to create a node with labels or properties passed in as parameters.
77+
78+
The following creates `labels` and `properties` parameters:
79+
80+
[source,cypher]
81+
----
82+
:param labels => (["Person"]);
83+
:param identityProperties => ({name: "Billy Reviewer"});
84+
:param onMatchProperties => ({placeOfBirth: "Stars of the milky way, Always at the time of sunrise."});
85+
----
86+
87+
The following match a node with labels and properties based on `labels` and `identityProperties`, furthermore sets a new property based on `onMatchProperties`:
88+
89+
[source,cypher]
90+
----
91+
CALL apoc.node.match($labels, $identityProperties, $onMatchProperties)
92+
YIELD node
93+
RETURN node;
94+
----
95+
96+
.Results
97+
[opts="header"]
98+
|===
99+
| node
100+
| (:Person {name: "Billy Reviewer", lastSeen: 2020-11-24T11:33:39.319Z, placeOfBirth: "Stars of the milky way, Always at the time of sunrise."})
101+
|===
102+
103+
104+
In addition, we can use the `apoc.node.match` along with the `apoc.load.json` to dynamically set nodes starting from a JSON.
105+
106+
For example, given the following dataset:
107+
108+
[source,cypher]
109+
----
110+
CREATE (giacomo:Person:Actor {name: 'Giacomino Poretti'}),
111+
(aldo:Person:Actor {name: 'Cataldo Baglio'}),
112+
(giovanni:Person:Actor {name: 'Giovanni Storti'})
113+
----
114+
115+
and the following `all.json` file:
116+
117+
[source,json]
118+
----
119+
[
120+
{
121+
"labels":[
122+
"Person",
123+
"Actor"
124+
],
125+
"matchProps":{
126+
"name":"Giacomino Poretti"
127+
},
128+
"setProps":{
129+
"bio":"Giacomo Poretti was born on April 26 1956 in Busto Garolfo",
130+
"Alias":"Tafazzi"
131+
}
132+
},
133+
{
134+
"labels":[
135+
"Person",
136+
"Actor"
137+
],
138+
"matchProps":{
139+
"name":"Giovanni Storti"
140+
},
141+
"setProps":{
142+
"bio":"Giovanni Storti was born ...",
143+
"Alias":"Rezzonico"
144+
}
145+
},
146+
{
147+
"labels":[
148+
"Person",
149+
"Actor"
150+
],
151+
"matchProps":{
152+
"name":"Cataldo Baglio"
153+
},
154+
"setProps":{
155+
"bio":"Cataldo Baglio was born somewhere",
156+
"Alias":"Ajeje"
157+
}
158+
}
159+
]
160+
161+
----
162+
163+
164+
we can execute the following query to MATCH and SET the `Person:Actor` nodes:
165+
166+
[source,cypher]
167+
----
168+
CALL apoc.load.json("all.json") YIELD value
169+
WITH value
170+
CALL apoc.node.match(value.labels, value.matchProps, value.setProps)
171+
YIELD node
172+
RETURN node
173+
----
174+
175+
.Results
176+
[opts="header"]
177+
|===
178+
| node
179+
| (:Actor:Person {name: "Giacomino Poretti",bio: "Giacomo Poretti was born on April 26 1956 in Busto Garolfo",Alias: "Tafazzi"})
180+
| (:Actor:Person {name: "Giovanni Storti",bio: "Giovanni Storti was born ...",Alias: "Rezzonico"})
181+
| (:Actor:Person {name: "Cataldo Baglio",bio: "Cataldo Baglio was born somewhere",Alias: "Ajeje"})
182+
|===
183+
184+
185+
186+
[[matching-relationship]]
187+
== Matching relationships
188+
189+
[.emphasis]
190+
"apoc.rel.match(startNode, relType, identProps:{key:value, ...}, endNode, onMatchProps:{key:value, ...})" - match relationship with dynamic type, with support for setting properties on match
191+
192+
=== Signature
193+
194+
[source]
195+
----
196+
apoc.rel.match(startNode :: NODE?, relationshipType :: STRING?, identProps :: MAP?, endNode :: NODE?, onMatchProps = {} :: MAP?) :: (rel :: RELATIONSHIP?)
197+
----
198+
199+
=== Input parameters
200+
[.procedures, opts=header]
201+
|===
202+
| Name | Type | Default | Description
203+
| startNode | NODE? | null | Start node of the MATCH pattern.
204+
| relationshipType | STRING? | null | Relationship type of the MATCH pattern.
205+
| identProps | MAP? | null | Properties on the relationships that are used for MATCH statement.
206+
| endNode | NODE? | null | End node of the MATCH pattern.
207+
| onMatchProps | MAP? | {} | Properties that are set when the relationship is matched.
208+
|===
209+
210+
=== Output parameters
211+
[.procedures, opts=header]
212+
|===
213+
| Name | Type
214+
|rel|RELATIONSHIP?
215+
|===
216+
217+
=== Usage Examples
218+
219+
The examples in this section are based on the following graph:
220+
221+
[source,cypher]
222+
----
223+
CREATE (p:Person {name: "Billy Reviewer"})
224+
CREATE (m:Movie {title:"spooky and goofy movie"})
225+
CREATE (p)-[REVIEW {lastSeen: date("1984-12-21")}]->(m);
226+
----
227+
228+
This procedure provides a more flexible and performant way of matching relationships than Cypher's https://neo4j.com/docs/cypher-manual/current/clauses/match/[`MATCH`^] clause.
229+
230+
The example below shows equivalent ways of matching an `REVIEW` relationship between the `Billy Reviewer` and a Movie nodes:
231+
232+
// tag::tabs[]
233+
[.tabs]
234+
235+
.apoc.rel.match
236+
[source,cypher]
237+
----
238+
MATCH (p:Person {name: "Billy Reviewer"})
239+
MATCH (m:Movie {title:"spooky and goofy movie"})
240+
CALL apoc.rel.match(
241+
p, "REVIEW",
242+
{lastSeen: date("1984-12-21")},
243+
m, {rating: 9.5}
244+
)
245+
YIELD rel
246+
RETURN rel;
247+
----
248+
249+
.MATCH clause
250+
[source,cypher]
251+
----
252+
MATCH (p:Person {name: "Billy Reviewer"})
253+
MATCH (m:Movie {title:"spooky and goofy movie"})
254+
MATCH (p)-[rel:REVIEW {lastSeen: date("1984-12-21")}]->(m)
255+
SET rel.rating = 9.5
256+
RETURN rel;
257+
----
258+
// end::tabs[]
259+
260+
If we run these queries, we'll see output as shown below:
261+
262+
.Results
263+
[opts="header"]
264+
|===
265+
| rel
266+
| [:REVIEW {lastSeen: 1984-12-21, rating: 9.5}]
267+
|===
268+
269+
But this procedure is mostly useful for matching relationships that have a dynamic relationship type or dynamic properties.
270+
For example, we might want to match a relationship with a type or properties passed in as parameters.
271+
272+
The following creates `relationshipType` and `properties` parameters:
273+
274+
[source,cypher]
275+
----
276+
:param relType => ("REVIEW");
277+
:param identityProperties => ({lastSeen: date("1984-12-21")});
278+
----
279+
280+
The following match a relationship with a type and properties based on the previously defined parameters:
281+
282+
[source,cypher]
283+
----
284+
MATCH (bill:Person {name: "Billy Reviewer"})
285+
MATCH (movie:Movie {title:"spooky and goofy movie"})
286+
CALL apoc.rel.match(bill, $relType, $identityProperties, movie, {}})
287+
YIELD rel
288+
RETURN rel;
289+
----
290+
291+
.Results
292+
[opts="header"]
293+
|===
294+
| rel
295+
| [:REVIEW {lastSeen: 1984-12-21, rating: 9.5}]
296+
|===
297+
298+
299+
In addition, we can use the `apoc.rel.match` along with the `apoc.load.json` to dynamically set nodes starting from a JSON.
300+
301+
For example, given the following dataset:
302+
303+
[source,cypher]
304+
----
305+
CREATE (giacomo:Person:Actor {name: 'Giacomino Poretti'}),
306+
(aldo:Person:Actor {name: 'Cataldo Baglio'}),
307+
(m:Movie {title: 'Three Men and a Leg', `y:ear`: 1997, `mean-rating`: 8, `release date`: date('1997-12-27')})
308+
WITH aldo, m
309+
CREATE (aldo)-[:ACTED_IN {role: 'Aldo'}]->(m),
310+
(aldo)-[:DIRECTED {role: 'Director'}]->(m)
311+
----
312+
313+
and the following `all.json` file
314+
(note that it leverage the elementId of start and end nodes, therefore the values are mutable):
315+
316+
[source,json]
317+
----
318+
[
319+
{
320+
"startNodeId": "4:b3d54d7b-2c64-4994-9a26-0bb2aa175291:0",
321+
"endNodeId": "4:b3d54d7b-2c64-4994-9a26-0bb2aa175291:0",
322+
"type":"ACTED_IN",
323+
"matchProps":{
324+
"role":"Aldo"
325+
},
326+
"setProps":{
327+
"ajeje":"Brazorf",
328+
"conte":"Dracula"
329+
}
330+
},
331+
{
332+
"startNodeId": "4:b3d54d7b-2c64-4994-9a26-0bb2aa175291:0",
333+
"endNodeId": "4:b3d54d7b-2c64-4994-9a26-0bb2aa175291:0",
334+
"type":"DIRECTED",
335+
"matchProps":{
336+
"role":"Director"
337+
},
338+
"setProps":{
339+
"description": "did stuff..",
340+
"alias":"i dunnoaaaaaa"
341+
}
342+
}
343+
]
344+
----
345+
346+
347+
we can execute the following query to MATCH and SET the relationships:
348+
349+
[source,cypher]
350+
----
351+
CALL apoc.load.json("all.json") YIELD value
352+
WITH value
353+
WHERE elementId(start) = value.startNodeId AND elementId(end) = value.endNodeId
354+
CALL apoc.rel.match(start, value.type, value.matchProps, end, value.setProps)
355+
YIELD rel
356+
RETURN rel
357+
----
358+
359+
.Results
360+
[opts="header"]
361+
|===
362+
| rel
363+
| [:ACTED_IN {role: "Aldo",conte: "Dracula",ajeje: "Brazorf"}]
364+
| (:Actor:Person {name: "Giovanni Storti",bio: "Giovanni Storti was born ...",Alias: "Rezzonico"})
365+
| [:DIRECTED {bio: "did stuff..",alias: "i dunno",role: "Director"}]
366+
|===

0 commit comments

Comments
 (0)