Skip to content

Commit d6b2f98

Browse files
Dynamic labels/types (#1098)
Co-authored-by: Stefano Ottolenghi <[email protected]>
1 parent afeeb5a commit d6b2f98

File tree

9 files changed

+425
-22
lines changed

9 files changed

+425
-22
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: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,61 @@ Nodes created: 2 +
205205
Properties set: 4
206206
|===
207207

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

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

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

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

303343
=== Import compressed CSV files
304344

modules/ROOT/pages/clauses/match.adoc

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

0 commit comments

Comments
 (0)