From d6b2f98a70aa03e91c33bf5d85fe2209eabbd0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:48:51 +0100 Subject: [PATCH] Dynamic labels/types (#1098) Co-authored-by: Stefano Ottolenghi --- modules/ROOT/images/graph_match_clause.svg | 2 +- .../gql-conformance/additional-cypher.adoc | 87 +++++++++++ modules/ROOT/pages/clauses/create.adoc | 53 ++++++- modules/ROOT/pages/clauses/load-csv.adoc | 40 +++++ modules/ROOT/pages/clauses/match.adoc | 139 +++++++++++++++++- modules/ROOT/pages/clauses/merge.adoc | 57 +++++++ modules/ROOT/pages/clauses/remove.adoc | 14 +- modules/ROOT/pages/clauses/set.adoc | 14 +- ...ions-additions-removals-compatibility.adoc | 41 +++++- 9 files changed, 425 insertions(+), 22 deletions(-) diff --git a/modules/ROOT/images/graph_match_clause.svg b/modules/ROOT/images/graph_match_clause.svg index d2481798f..393b3fe23 100644 --- a/modules/ROOT/images/graph_match_clause.svg +++ b/modules/ROOT/images/graph_match_clause.svg @@ -1 +1 @@ -DIRECTEDACTED_INrole:'Gordon Gekko'ACTED_INrole:'Carl Fox'ACTED_INrole:'President Andrew Shepherd'ACTED_INrole:'A.J. MacInerney'ACTED_INrole:'Bud Fox'DIRECTEDPersonname:'Oliver Stone'Movietitle:'Wall Street'Personname:'Michael Douglas'Personname:'Martin Sheen'Movietitle:'The American President'Personname:'Charlie Sheen'Personname:'Rob Reiner' +DIRECTEDACTED_INrole:'Gordon Gekko'ACTED_INrole:'Carl Fox'ACTED_INrole:'President Andrew Shepherd'ACTED_INrole:'A.J. MacInerney'ACTED_INrole:'Bud Fox'DIRECTEDPersonDirectorname:'Oliver Stone'Movietitle:'Wall Street'PersonActorname:'Michael Douglas'PersonActorname:'Martin Sheen'Movietitle:'The American President'PersonActorname:'Charlie Sheen'PersonDirectorname:'Rob Reiner' \ No newline at end of file diff --git a/modules/ROOT/pages/appendix/gql-conformance/additional-cypher.adoc b/modules/ROOT/pages/appendix/gql-conformance/additional-cypher.adoc index 46134dceb..837a32330 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/additional-cypher.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/additional-cypher.adoc @@ -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 diff --git a/modules/ROOT/pages/clauses/create.adoc b/modules/ROOT/pages/clauses/create.adoc index 9749ac375..f2ee2cd58 100644 --- a/modules/ROOT/pages/clauses/create.adoc +++ b/modules/ROOT/pages/clauses/create.adoc @@ -205,10 +205,61 @@ Nodes created: 2 + Properties set: 4 |=== +[role=label--new-5.26] +[[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:$()) +CREATE ()-[r:$()]->() +---- + +The expression must evaluate to a `STRING NOT NULL | LIST NOT NULL` value. +Using a `LIST` 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* 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*(wallStreet), (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), @@ -124,8 +124,10 @@ The above query uses the xref:functions/list.adoc#functions-labels[`labels()`] a [role="queryresult",options="header,footer",cols="2* Label expressions]. @@ -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:$()) +MATCH (n:$any()) +MATCH (n:$all()) +---- + +[NOTE] +`MATCH (n:$all())` is functionally equivalent to `MATCH (n:$())`. + +.Syntax for matching relationship types dynamically +[source, syntax] +---- +MATCH ()-[r:$())]->() +MATCH ()-[r:$any()]->() +MATCH ()-[r:$all())]->() +---- + +The expression must evaluate to a `STRING NOT NULL | LIST NOT NULL` value. +If you use a `LIST` 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*() +RETURN relationshipType, count(r) AS relationshipCount +---- + +.Result +[role="queryresult",options="header,footer",cols="2* Protecting against Cypher injection]). + +.Syntax for merging nodes and relationships dynamically +[source, syntax] +---- +MERGE (n:$()) +MERGE ()-[r:$()]->() +---- + +The expression must evaluate to a `STRING NOT NULL | LIST NOT NULL` value. +Using a `LIST` with more than one item when merging a relationship using dynamic relationship types will fail. +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. + +.Parameters +[source, parameters] +---- +{ + "nodeLabels": ["Person", "Director"], + "relType": "DIRECTED", + "movies": ["Ladybird", "Little Women", "Barbie"] +} +---- + +.Merge nodes and relationships using dynamic node labels and relationship types +[source, cypher] +---- +MERGE (greta:$($nodeLabels) {name: 'Greta Gerwig'}) +WITH greta +UNWIND $movies AS movieTitle +MERGE (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="3* Protecting against Cypher injection]). [source, syntax] ---- @@ -82,7 +84,6 @@ REMOVE n[key] The dynamically calculated key must evaluate to a `STRING` value. This query creates a copy of every property on the nodes: - .Query [source, cypher, indent=0] ---- @@ -131,18 +132,19 @@ Labels removed: 1 |=== [role=label--new-5.24] -[[remove-remove-a-label-dynamically-from-a-node]] -== Dynamically removing a label +[[dynamic-remove-node-label]] +== Dynamically remove a node label `REMOVE` can be used to remove a label on a node even when the label is not statically known. - [source, syntax] ---- MATCH (n) REMOVE n:$(expr) ---- +The expression must evaluate to a `STRING NOT NULL | LIST NOT NULL` value. + .Query [source, cypher, indent=0] ---- diff --git a/modules/ROOT/pages/clauses/set.adoc b/modules/ROOT/pages/clauses/set.adoc index 6976c8abe..d90a891d1 100644 --- a/modules/ROOT/pages/clauses/set.adoc +++ b/modules/ROOT/pages/clauses/set.adoc @@ -162,10 +162,12 @@ Properties set: 1 [role=label--new-5.24] -[[set-dynamically-a-property]] -== Dynamically setting or updating a property +[[dynamic-set-property]] +== Dynamically set or update a property `SET` can be used to set or update a property on a node or relationship even when the property key name is not statically known. +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]). [source, syntax] ---- @@ -541,15 +543,15 @@ Labels added: 1 |=== [role=label--new-5.24] -[[set-set-a-dynamic-label-on-a-node]] -== Dynamically setting a label +[[dynamic-set-node-label]] +== Dynamically set a node label `SET` can be used to set a label on a node even when the label is not statically known. [source, syntax] ---- MATCH (n) -SET n:$(expr) +SET n:$() ---- .Query @@ -629,7 +631,7 @@ Labels added: 2 |=== [role=label--new-5.24] -[[set-set-multiple-dynamic-labels-on-a-node]] +[[dynamic-set-multiple-node-labels]] == Set multiple labels dynamically on a node It is possible to set multiple labels dynamically using a `LIST` and/or by chaining them separately with a `:`: diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 9decf9b67..432f5b253 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -184,6 +184,45 @@ GRANT READ {*} ON GRAPH * FOR (n) WHERE n.createdAt > date('2024-10-25') TO regu |=== +=== New features + +[cols="2", options="header"] +|=== +| Feature +| Details + +a| +label:functionality[] +label:new[] +[source, cypher, role="noheader"] +---- +MATCH (n:$($label)), + ()-[r:$($type))]->() +---- + +[source, cypher, role="noheader"] +---- +CREATE (n:$($label)), + ()-[r:$($type)]->() +---- + +[source, cypher, role="noheader"] +---- +MERGE (n:$($label)), + ()-[r:$($type)]->() +---- + +[source, cypher, role="noheader"] +---- +LOAD CSV WITH HEADERS FROM 'file:///artists-with-headers.csv' AS line +CREATE (n:$(line.label) {name: line.Name}) +---- + +| Added the ability to dynamically reference node labels and relationship types in xref:clauses/match.adoc#dynamic-match[`MATCH`], xref:clauses/create.adoc#dynamic-create[`CREATE`], and xref:clauses/merge.adoc#dynamic-merge[`MERGE`] clauses. +Also introduced the ability to specify CSV columns dynamically when using xref:clauses/load-csv.adoc#dynamic-load[`LOAD CSV`]. +|=== + + [[cypher-deprecations-additions-removals-5.25]] == Neo4j 5.25 @@ -319,7 +358,7 @@ label:new[] SET n[$prop] = "hello world" REMOVE n[$prop] ---- -| Added the ability to dynamically reference properties in xref:clauses/set.adoc#set-dynamically-a-property[SET] and xref:clauses/remove.adoc#remove-remove-a-property-dynamically[REMOVE] clauses. +| Added the ability to dynamically reference properties in xref:clauses/set.adoc#dynamic-set-property[SET] and xref:clauses/remove.adoc#dynamic-remove-property[REMOVE] clauses. a| label:functionality[]