Skip to content

Commit

Permalink
Dynamic labels/types (neo4j#1098)
Browse files Browse the repository at this point in the history
Co-authored-by: Stefano Ottolenghi <[email protected]>
  • Loading branch information
JPryce-Aklundh and stefano-ottolenghi committed Nov 19, 2024
1 parent 3667122 commit 7c7c094
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 27 deletions.
2 changes: 1 addition & 1 deletion modules/ROOT/images/graph_match_clause.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 87 additions & 0 deletions modules/ROOT/pages/appendix/gql-conformance/additional-cypher.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,93 @@ Either the pattern already exists, or it needs to be created.
| Syntactic construct for creating a `LIST` based on matchings of a pattern.
|===

[[dynamic-queries]]
== Dynamic queries

Node labels, relationship types, properties, and CSV columns can be referenced dynamically using Cypher.
This allows for more flexible queries and mitigates the risk of Cypher injection.
(For more information about Cypher injection, see link:https://neo4j.com/developer/kb/protecting-against-cypher-injection/[Neo4j Knowledge Base -> Protecting against Cypher injection]).

[options="header", cols="2a,5a"]
|===
| Cypher feature
| Description

a|
[source, cypher, role="noheader"]
----
MATCH (n:$($label)),
()-[r:$($type))]->()
----

| xref:clauses/match.adoc#dynamic-match[`MATCH` nodes and relationships using dynamic node labels and relationship types]

a|
[source, cypher, role="noheader"]
----
CREATE (n:$($label)),
()-[r:$($type)]->()
----

| xref:clauses/create.adoc#dynamic-create[`CREATE` nodes and relationships using dynamic node labels and relationship types]

a|
[source, cypher, role="noheader"]
----
MERGE (n:$($label)),
()-[r:$($type)]->()
----

| xref:clauses/merge.adoc#dynamic-merge[`MERGE` nodes and relationships using dynamic node labels and relationship types]

a|
[source, cypher, role="noheader"]
----
LOAD CSV WITH HEADERS FROM 'file:///artists-with-headers.csv' AS line
CREATE (n:$(line.label) {name: line.Name})
----

| xref:clauses/load-csv.adoc#dynamic-columns[Import CSV files using dynamic columns]


a|
[source, cypher, role="noheader"]
----
MATCH (n)
SET n[$key] = value
----

| xref:clauses/set.adoc#dynamic-set-property[Dynamically `SET` or update a property]

a|
[source, cypher, role="noheader"]
----
MATCH (n:Label)
SET n:$(n.property)
----

| xref:clauses/set.adoc#dynamic-set-node-label[Dynamically `SET` a node label]

a|
[source, cypher, role="noheader"]
----
MATCH (n {name: 'Peter'})
REMOVE n:$($label)
----

| xref:clauses/remove.adoc#dynamic-remove-property[Dynamically `REMOVE` a property]

a|
[source, cypher, role="noheader"]
----
MATCH (n {name: 'Peter'})
REMOVE n:$($label)
----

| xref:clauses/remove.adoc#dynamic-remove-node-label[Dynamically `REMOVE` a node label]

|===


[[functions]]
== Functions
Expand Down
51 changes: 50 additions & 1 deletion modules/ROOT/pages/clauses/create.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,59 @@ Nodes created: 2 +
Properties set: 4
|===

[[dynamic-create]]
== CREATE using dynamic node labels and relationship types

Node labels and relationship types can be referenced dynamically in expressions, parameters, and variables when creating nodes and relationships.
This allows for more flexible queries and mitigates the risk of Cypher injection.
(For more information about Cypher injection, see link:https://neo4j.com/developer/kb/protecting-against-cypher-injection/[Neo4j Knowledge Base -> Protecting against Cypher injection]).

.Syntax for creating nodes and relationships dynamically
[source, syntax]
----
CREATE (n:$(<expr>))
CREATE ()-[r:$(<expr>)]->()
----

The expression must evaluate to a `STRING NOT NULL | LIST<STRING NOT NULL> NOT NULL` value.
Using a `LIST<STRING>` with more than one item when creating a relationship using dynamic relationship types will fail.
This is because a relationship can only have exactly one type.

.Parameters
[source, parameters]
----
{
"nodeLabels": ["Person", "Director"],
"relType": "DIRECTED",
"movies": ["Ladybird", "Little Women", "Barbie"]
}
----

.Create nodes and relationships using dynamic node labels and relationship types
[source, cypher]
----
CREATE (greta:$($nodeLabels) {name: 'Greta Gerwig'})
WITH greta
UNWIND $movies AS movieTitle
CREATE (greta)-[rel:$($relType)]->(m:Movie {title: movieTitle})
RETURN greta.name AS name, labels(greta) AS labels, type(rel) AS relType, collect(m.title) AS movies
----

.Result
[role="queryresult",options="footer",cols="4*<m"]
|===
| name | labels | relType | movies

| "Greta Gerwig"
| ["Person", "Director"]
| "DIRECTED"
| ["Ladybird", "Little Women", "Barbie"]
4+d|Rows: 1 +
|===

[[insert-as-synonym-of-create]]
== `INSERT` as a synonym of `CREATE`

`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[].
However, `INSERT` requires that multiple labels are separated by an ampersand `&` and not by colon `:`.

Expand Down
40 changes: 40 additions & 0 deletions modules/ROOT/pages/clauses/load-csv.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,46 @@ Added 4 nodes, Set 8 properties, Added 4 labels
|===
====

[role=label--new-5.26]
[[dynamic-columns]]
=== Import CSV files using dynamic columns

CSV columns can be referenced dynamically to map labels to nodes in the graph.
This enables flexible data handling, allowing labels to be be populated from CSV column values without manually specifying each entry.
It also mitigates the risk of Cypher injection.
(For more information about Cypher injection, see link:https://neo4j.com/developer/kb/protecting-against-cypher-injection/[Neo4j Knowledge Base -> Protecting against Cypher injection]).

.bands-with-headers.csv
[source, csv, filename="artists-with-headers.csv"]
----
Id,Label,Name
1,Band,The Beatles
2,Band,The Rolling Stones
3,Band,Pink Floyd
4,Band,Led Zeppelin
----

.Query
[source, cypher, role=test-skip]
----
LOAD CSV WITH HEADERS FROM 'file:///bands-with-headers.csv' AS line
MERGE (n:$(line.Label) {name: line.Name})
RETURN n AS bandNodes
----

.Result
[role="queryresult",options="header,footer",cols="1*<m"]
|===
| bandNodes

| (:Band {name: 'The Beatles'})
| (:Band {name: 'The Rolling Stones'})
| (:Band {name: 'Pink Floyd'})
| (:Band {name: 'Led Zeppelin'})

1+d|Rows: 4 +
Added 4 nodes, Set 4 properties, Added 4 labels
|===

=== Import compressed CSV files

Expand Down
139 changes: 132 additions & 7 deletions modules/ROOT/pages/clauses/match.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ To recreate the graph, run the following query against an empty Neo4j database:

[source, cypher, role=test-setup]
----
CREATE (charlie:Person {name: 'Charlie Sheen'}),
(martin:Person {name: 'Martin Sheen'}),
(michael:Person {name: 'Michael Douglas'}),
(oliver:Person {name: 'Oliver Stone'}),
(rob:Person {name: 'Rob Reiner'}),
CREATE (charlie:Person:Actor {name: 'Charlie Sheen'}),
(martin:Person:Actor {name: 'Martin Sheen'}),
(michael:Person:Actor {name: 'Michael Douglas'}),
(oliver:Person:Director {name: 'Oliver Stone'}),
(rob:Person:Director {name: 'Rob Reiner'}),
(wallStreet:Movie {title: 'Wall Street'}),
(charlie)-[:ACTED_IN {role: 'Bud Fox'}]->(wallStreet),
(martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet),
Expand Down Expand Up @@ -124,8 +124,10 @@ The above query uses the xref:functions/list.adoc#functions-labels[`labels()`] a
[role="queryresult",options="header,footer",cols="2*<m"]
|===
| label | labelCount
| ["Person"] | 5
2+d| Rows: 1
| ["Person", "Actor"] | 3
| ["Person", "Director"] | 2

2+d| Rows: 2
|===

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

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

[role=label--new-5.26]
[[dynamic-match]]
== MATCH using dynamic node labels and relationship types

Node labels and relationship types can be referenced dynamically in expressions, parameters, and variables when matching nodes and relationships.
This allows for more flexible queries and mitigates the risk of Cypher injection.
(For more information about Cypher injection, see link:https://neo4j.com/developer/kb/protecting-against-cypher-injection/[Neo4j Knowledge Base -> Protecting against Cypher injection]).

.Syntax for matching node labels dynamically
[source, syntax]
----
MATCH (n:$(<expr>))
MATCH (n:$any(<expr>))
MATCH (n:$all(<expr>))
----

[NOTE]
`MATCH (n:$all(<expr>))` is functionally equivalent to `MATCH (n:$(<expr>))`.

.Syntax for matching relationship types dynamically
[source, syntax]
----
MATCH ()-[r:$(<expr>))]->()
MATCH ()-[r:$any(<expr>)]->()
MATCH ()-[r:$all(<expr>))]->()
----

The expression must evaluate to a `STRING NOT NULL | LIST<STRING NOT NULL> NOT NULL` value.
If you use a `LIST<STRING>` with more than one item in a relationship pattern with dynamic relationship types, no results will be returned.
This is because a relationship can only have exactly one type.

[NOTE]
Queries using dynamic values may not be as performant as those using static values.
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.

.Match labels dynamically
[source, cypher]
----
WITH ["Person", "Director"] AS labels
MATCH (directors:$(labels))
RETURN directors
----

.Result
[role="queryresult",options="header,footer",cols="1*<m"]
|===
| directors

| (:Person:Director {name: "Oliver Stone"})
| (:Person:Director {name: "Rob Reiner"})

1+d|Rows: 2
|===

.Match nodes dynamically using the `any()` function
[source, cypher]
----
MATCH (n:$any(["Movie", "Actor"]))
RETURN n AS nodes
----

.Result
[role="queryresult",options="header,footer",cols="1*<m"]
|===
| nodes

| (:Person:Actor {name: "Charlie Sheen"})
| (:Person:Actor {name: "Martin Sheen"})
| (:Person:Actor {name: "Michael Douglas"})
| (:Movie {title: "Wall Street"})
| (:Movie {title: "The American President"})

1+d|Rows: 5
|===


.Parameter
[source, parameters]
----
{
"label": "Movie"
}
----

.Match nodes dynamically using a parameter
[source, cypher]
----
MATCH (movie:$($label))
RETURN movie.title AS movieTitle
----

.Result
[role="queryresult",options="header,footer",cols="1*<m"]
|===
| movieTitle

| "Wall Street"
| "The American President"

1+d|Rows: 2
|===


.Match relationships dynamically using a variable
[source, cypher]
----
CALL db.relationshipTypes()
YIELD relationshipType
MATCH ()-[r:$(relationshipType)]->()
RETURN relationshipType, count(r) AS relationshipCount
----

.Result
[role="queryresult",options="header,footer",cols="2*<m"]
|===
| relationshipType | relationshipCount

| "ACTED_IN" | 5
| "DIRECTED" | 2

2+d|Rows: 2
|===

Loading

0 comments on commit 7c7c094

Please sign in to comment.