Skip to content

Commit 562f072

Browse files
Cypher 25 Dynamic labels/types (neo4j#1114)
Co-authored-by: Stefano Ottolenghi <[email protected]>
1 parent 3667122 commit 562f072

File tree

9 files changed

+420
-27
lines changed

9 files changed

+420
-27
lines changed

modules/ROOT/images/graph_match_clause.svg

Lines changed: 1 addition & 1 deletion
Loading

modules/ROOT/pages/appendix/gql-conformance/additional-cypher.adoc

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,93 @@ Either the pattern already exists, or it needs to be created.
7878
| Syntactic construct for creating a `LIST` based on matchings of a pattern.
7979
|===
8080

81+
[[dynamic-queries]]
82+
== Dynamic queries
83+
84+
Node labels, relationship types, properties, and CSV columns can be referenced dynamically using Cypher.
85+
This allows for more flexible queries and mitigates the risk of Cypher injection.
86+
(For more information about Cypher injection, see link:https://neo4j.com/developer/kb/protecting-against-cypher-injection/[Neo4j Knowledge Base -> Protecting against Cypher injection]).
87+
88+
[options="header", cols="2a,5a"]
89+
|===
90+
| Cypher feature
91+
| Description
92+
93+
a|
94+
[source, cypher, role="noheader"]
95+
----
96+
MATCH (n:$($label)),
97+
()-[r:$($type))]->()
98+
----
99+
100+
| xref:clauses/match.adoc#dynamic-match[`MATCH` nodes and relationships using dynamic node labels and relationship types]
101+
102+
a|
103+
[source, cypher, role="noheader"]
104+
----
105+
CREATE (n:$($label)),
106+
()-[r:$($type)]->()
107+
----
108+
109+
| xref:clauses/create.adoc#dynamic-create[`CREATE` nodes and relationships using dynamic node labels and relationship types]
110+
111+
a|
112+
[source, cypher, role="noheader"]
113+
----
114+
MERGE (n:$($label)),
115+
()-[r:$($type)]->()
116+
----
117+
118+
| xref:clauses/merge.adoc#dynamic-merge[`MERGE` nodes and relationships using dynamic node labels and relationship types]
119+
120+
a|
121+
[source, cypher, role="noheader"]
122+
----
123+
LOAD CSV WITH HEADERS FROM 'file:///artists-with-headers.csv' AS line
124+
CREATE (n:$(line.label) {name: line.Name})
125+
----
126+
127+
| xref:clauses/load-csv.adoc#dynamic-columns[Import CSV files using dynamic columns]
128+
129+
130+
a|
131+
[source, cypher, role="noheader"]
132+
----
133+
MATCH (n)
134+
SET n[$key] = value
135+
----
136+
137+
| xref:clauses/set.adoc#dynamic-set-property[Dynamically `SET` or update a property]
138+
139+
a|
140+
[source, cypher, role="noheader"]
141+
----
142+
MATCH (n:Label)
143+
SET n:$(n.property)
144+
----
145+
146+
| xref:clauses/set.adoc#dynamic-set-node-label[Dynamically `SET` a node label]
147+
148+
a|
149+
[source, cypher, role="noheader"]
150+
----
151+
MATCH (n {name: 'Peter'})
152+
REMOVE n:$($label)
153+
----
154+
155+
| xref:clauses/remove.adoc#dynamic-remove-property[Dynamically `REMOVE` a property]
156+
157+
a|
158+
[source, cypher, role="noheader"]
159+
----
160+
MATCH (n {name: 'Peter'})
161+
REMOVE n:$($label)
162+
----
163+
164+
| xref:clauses/remove.adoc#dynamic-remove-node-label[Dynamically `REMOVE` a node label]
165+
166+
|===
167+
81168

82169
[[functions]]
83170
== Functions

modules/ROOT/pages/clauses/create.adoc

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,59 @@ Nodes created: 2 +
205205
Properties set: 4
206206
|===
207207

208+
[[dynamic-create]]
209+
== CREATE using dynamic node labels and relationship types
210+
211+
Node labels and relationship types can be referenced dynamically in expressions, parameters, and variables when creating nodes and relationships.
212+
This allows for more flexible queries and mitigates the risk of Cypher injection.
213+
(For more information about Cypher injection, see link:https://neo4j.com/developer/kb/protecting-against-cypher-injection/[Neo4j Knowledge Base -> Protecting against Cypher injection]).
214+
215+
.Syntax for creating nodes and relationships dynamically
216+
[source, syntax]
217+
----
218+
CREATE (n:$(<expr>))
219+
CREATE ()-[r:$(<expr>)]->()
220+
----
221+
222+
The expression must evaluate to a `STRING NOT NULL | LIST<STRING NOT NULL> NOT NULL` value.
223+
Using a `LIST<STRING>` with more than one item when creating a relationship using dynamic relationship types will fail.
224+
This is because a relationship can only have exactly one type.
225+
226+
.Parameters
227+
[source, parameters]
228+
----
229+
{
230+
"nodeLabels": ["Person", "Director"],
231+
"relType": "DIRECTED",
232+
"movies": ["Ladybird", "Little Women", "Barbie"]
233+
}
234+
----
235+
236+
.Create nodes and relationships using dynamic node labels and relationship types
237+
[source, cypher]
238+
----
239+
CREATE (greta:$($nodeLabels) {name: 'Greta Gerwig'})
240+
WITH greta
241+
UNWIND $movies AS movieTitle
242+
CREATE (greta)-[rel:$($relType)]->(m:Movie {title: movieTitle})
243+
RETURN greta.name AS name, labels(greta) AS labels, type(rel) AS relType, collect(m.title) AS movies
244+
----
245+
246+
.Result
247+
[role="queryresult",options="footer",cols="4*<m"]
248+
|===
249+
| name | labels | relType | movies
250+
251+
| "Greta Gerwig"
252+
| ["Person", "Director"]
253+
| "DIRECTED"
254+
| ["Ladybird", "Little Women", "Barbie"]
255+
4+d|Rows: 1 +
256+
|===
208257

209258
[[insert-as-synonym-of-create]]
210259
== `INSERT` as a synonym of `CREATE`
211-
260+
212261
`INSERT` can be used as a synonym to `CREATE` for creating nodes and relationships, and was introduced as part of Cypher's xref:appendix/gql-conformance/index.adoc[].
213262
However, `INSERT` requires that multiple labels are separated by an ampersand `&` and not by colon `:`.
214263

modules/ROOT/pages/clauses/load-csv.adoc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,45 @@ Added 4 nodes, Set 8 properties, Added 4 labels
299299
|===
300300
====
301301

302+
[[dynamic-columns]]
303+
=== Import CSV files using dynamic columns
304+
305+
CSV columns can be referenced dynamically to map labels to nodes in the graph.
306+
This enables flexible data handling, allowing labels to be be populated from CSV column values without manually specifying each entry.
307+
It also mitigates the risk of Cypher injection.
308+
(For more information about Cypher injection, see link:https://neo4j.com/developer/kb/protecting-against-cypher-injection/[Neo4j Knowledge Base -> Protecting against Cypher injection]).
309+
310+
.bands-with-headers.csv
311+
[source, csv, filename="artists-with-headers.csv"]
312+
----
313+
Id,Label,Name
314+
1,Band,The Beatles
315+
2,Band,The Rolling Stones
316+
3,Band,Pink Floyd
317+
4,Band,Led Zeppelin
318+
----
319+
320+
.Query
321+
[source, cypher, role=test-skip]
322+
----
323+
LOAD CSV WITH HEADERS FROM 'file:///bands-with-headers.csv' AS line
324+
MERGE (n:$(line.Label) {name: line.Name})
325+
RETURN n AS bandNodes
326+
----
327+
328+
.Result
329+
[role="queryresult",options="header,footer",cols="1*<m"]
330+
|===
331+
| bandNodes
332+
333+
| (:Band {name: 'The Beatles'})
334+
| (:Band {name: 'The Rolling Stones'})
335+
| (:Band {name: 'Pink Floyd'})
336+
| (:Band {name: 'Led Zeppelin'})
337+
338+
1+d|Rows: 4 +
339+
Added 4 nodes, Set 4 properties, Added 4 labels
340+
|===
302341

303342
=== Import compressed CSV files
304343

modules/ROOT/pages/clauses/match.adoc

Lines changed: 131 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ To recreate the graph, run the following query against an empty Neo4j database:
1717

1818
[source, cypher, role=test-setup]
1919
----
20-
CREATE (charlie:Person {name: 'Charlie Sheen'}),
21-
(martin:Person {name: 'Martin Sheen'}),
22-
(michael:Person {name: 'Michael Douglas'}),
23-
(oliver:Person {name: 'Oliver Stone'}),
24-
(rob:Person {name: 'Rob Reiner'}),
20+
CREATE (charlie:Person:Actor {name: 'Charlie Sheen'}),
21+
(martin:Person:Actor {name: 'Martin Sheen'}),
22+
(michael:Person:Actor {name: 'Michael Douglas'}),
23+
(oliver:Person:Director {name: 'Oliver Stone'}),
24+
(rob:Person:Director {name: 'Rob Reiner'}),
2525
(wallStreet:Movie {title: 'Wall Street'}),
2626
(charlie)-[:ACTED_IN {role: 'Bud Fox'}]->(wallStreet),
2727
(martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet),
@@ -124,8 +124,10 @@ The above query uses the xref:functions/list.adoc#functions-labels[`labels()`] a
124124
[role="queryresult",options="header,footer",cols="2*<m"]
125125
|===
126126
| label | labelCount
127-
| ["Person"] | 5
128-
2+d| Rows: 1
127+
| ["Person", "Actor"] | 3
128+
| ["Person", "Director"] | 2
129+
130+
2+d| Rows: 2
129131
|===
130132

131133
For a list of all label expressions supported by Cypher, see xref:patterns/reference.adoc#label-expressions[Patterns -> Label expressions].
@@ -490,3 +492,125 @@ The above query uses the xref:functions/aggregating.adoc#functions-collect[`coll
490492

491493
For more information about how Cypher queries work, see xref:clauses/clause-composition.adoc[].
492494

495+
[[dynamic-match]]
496+
== MATCH using dynamic node labels and relationship types
497+
498+
Node labels and relationship types can be referenced dynamically in expressions, parameters, and variables when matching nodes and relationships.
499+
This allows for more flexible queries and mitigates the risk of Cypher injection.
500+
(For more information about Cypher injection, see link:https://neo4j.com/developer/kb/protecting-against-cypher-injection/[Neo4j Knowledge Base -> Protecting against Cypher injection]).
501+
502+
.Syntax for matching node labels dynamically
503+
[source, syntax]
504+
----
505+
MATCH (n:$(<expr>))
506+
MATCH (n:$any(<expr>))
507+
MATCH (n:$all(<expr>))
508+
----
509+
510+
[NOTE]
511+
`MATCH (n:$all(<expr>))` is functionally equivalent to `MATCH (n:$(<expr>))`.
512+
513+
.Syntax for matching relationship types dynamically
514+
[source, syntax]
515+
----
516+
MATCH ()-[r:$(<expr>))]->()
517+
MATCH ()-[r:$any(<expr>)]->()
518+
MATCH ()-[r:$all(<expr>))]->()
519+
----
520+
521+
The expression must evaluate to a `STRING NOT NULL | LIST<STRING NOT NULL> NOT NULL` value.
522+
If you use a `LIST<STRING>` with more than one item in a relationship pattern with dynamic relationship types, no results will be returned.
523+
This is because a relationship can only have exactly one type.
524+
525+
[NOTE]
526+
Queries using dynamic values may not be as performant as those using static values.
527+
This is because the xref:planning-and-tuning/execution-plans.adoc[Cypher planner] uses statically available information when planning queries to determine whether to use an xref:indexes/search-performance-indexes/overview.adoc[index] or not, and this is not possible when using dynamic values.
528+
529+
.Match labels dynamically
530+
[source, cypher]
531+
----
532+
WITH ["Person", "Director"] AS labels
533+
MATCH (directors:$(labels))
534+
RETURN directors
535+
----
536+
537+
.Result
538+
[role="queryresult",options="header,footer",cols="1*<m"]
539+
|===
540+
| directors
541+
542+
| (:Person:Director {name: "Oliver Stone"})
543+
| (:Person:Director {name: "Rob Reiner"})
544+
545+
1+d|Rows: 2
546+
|===
547+
548+
.Match nodes dynamically using the `any()` function
549+
[source, cypher]
550+
----
551+
MATCH (n:$any(["Movie", "Actor"]))
552+
RETURN n AS nodes
553+
----
554+
555+
.Result
556+
[role="queryresult",options="header,footer",cols="1*<m"]
557+
|===
558+
| nodes
559+
560+
| (:Person:Actor {name: "Charlie Sheen"})
561+
| (:Person:Actor {name: "Martin Sheen"})
562+
| (:Person:Actor {name: "Michael Douglas"})
563+
| (:Movie {title: "Wall Street"})
564+
| (:Movie {title: "The American President"})
565+
566+
1+d|Rows: 5
567+
|===
568+
569+
570+
.Parameter
571+
[source, parameters]
572+
----
573+
{
574+
"label": "Movie"
575+
}
576+
----
577+
578+
.Match nodes dynamically using a parameter
579+
[source, cypher]
580+
----
581+
MATCH (movie:$($label))
582+
RETURN movie.title AS movieTitle
583+
----
584+
585+
.Result
586+
[role="queryresult",options="header,footer",cols="1*<m"]
587+
|===
588+
| movieTitle
589+
590+
| "Wall Street"
591+
| "The American President"
592+
593+
1+d|Rows: 2
594+
|===
595+
596+
597+
.Match relationships dynamically using a variable
598+
[source, cypher]
599+
----
600+
CALL db.relationshipTypes()
601+
YIELD relationshipType
602+
MATCH ()-[r:$(relationshipType)]->()
603+
RETURN relationshipType, count(r) AS relationshipCount
604+
----
605+
606+
.Result
607+
[role="queryresult",options="header,footer",cols="2*<m"]
608+
|===
609+
| relationshipType | relationshipCount
610+
611+
| "ACTED_IN" | 5
612+
| "DIRECTED" | 2
613+
614+
2+d|Rows: 2
615+
|===
616+

0 commit comments

Comments
 (0)