diff --git a/modules/ROOT/images/call_subquery_graph.svg b/modules/ROOT/images/call_subquery_graph.svg index 932cf79da..e7e2e2629 100644 --- a/modules/ROOT/images/call_subquery_graph.svg +++ b/modules/ROOT/images/call_subquery_graph.svg @@ -1,9 +1,9 @@ - - + + - - + + - + diff --git a/modules/ROOT/pages/clauses/create.adoc b/modules/ROOT/pages/clauses/create.adoc index 4f674b8bc..b9f31e390 100644 --- a/modules/ROOT/pages/clauses/create.adoc +++ b/modules/ROOT/pages/clauses/create.adoc @@ -234,6 +234,7 @@ This is because a relationship can only have exactly one type. ---- .Create nodes and relationships using dynamic node labels and relationship types +// tag::clauses_create_dynamic_create[] [source, cypher] ---- CREATE (greta:$($nodeLabels) {name: 'Greta Gerwig'}) @@ -242,6 +243,7 @@ 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 ---- +// end::clauses_create_dynamic_create[] .Result [role="queryresult",options="footer",cols="4* Protecting against Cypher injection]). .bands-with-headers.csv -[source, csv, filename="artists-with-headers.csv"] +[source, csv, filename="bands-with-headers.csv"] ---- Id,Label,Name 1,Band,The Beatles @@ -318,12 +318,14 @@ Id,Label,Name ---- .Query -[source, cypher, role=test-skip] +// tag::clauses_load_csv_dynamic_columns[] +[source, cypher] ---- LOAD CSV WITH HEADERS FROM 'file:///bands-with-headers.csv' AS line MERGE (n:$(line.Label) {name: line.Name}) RETURN n AS bandNodes ---- +// end::clauses_load_csv_dynamic_columns[] .Result [role="queryresult",options="header,footer",cols="1*() RETURN relationshipType, count(r) AS relationshipCount ---- +// end::clauses_match_dynamic_match_variable[] + .Result [role="queryresult",options="header,footer",cols="2*(m:Movie {title: movieTitle}) RETURN greta.name AS name, labels(greta) AS labels, type(rel) AS relType, collect(m.title) AS movies ---- +// end::clauses_merge_dynamic_merge[] .Result [role="queryresult",options="footer",cols="3*(m) SET n = properties(r) | Feature | Details +a| +label:functionality[] +label:updated[] +[source, cypher, role=noheader] +---- +MATCH SHORTEST $param (:A)-[:R]->{0,10}(:B) +---- + +[source, cypher, role=noheader] +---- +MATCH p = ANY $param (:A)-[:R]->{0,10}(:B) +---- + +[source, cypher, role=noheader] +---- +MATCH SHORTEST $param GROUPS (:A)-[:R]->{0,10}(:B) +---- + +a| Introduced the allowance of parameters to the xref:patterns/shortest-paths.adoc[SHORTEST and ANY path patterns]. + a| label:functionality[] label:updated[] @@ -375,6 +395,45 @@ Also introduced the ability to specify CSV columns dynamically when using xref:c |=== +=== 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 @@ -415,6 +474,27 @@ The maximum limit is set to 16,383 characters in an identifier. This means that node labels, relationship types, and property keys cannot include more than 16,383 characters. |=== +=== Updated features + +[cols="2", options="header"] +|=== +| Feature +| Details + +a| +label:functionality[] +label:updated[] +[source, cypher, role="noheader"] +---- +CREATE (n:Label {property: 'name'}), +()-[r:REL_TYPE]->() +---- +| Neo4j's link:{neo4j-docs-base-uri}/operations-manual/{page-version}/database-internals/store-formats/#store-format-overview[block format] now implements xref:appendix/gql-conformance/index.adoc[GQL's] limit on the maximum length of identifiers. + +The maximum limit is set to 16,383 characters in an identifier. +This means that node labels, relationship types, and property keys cannot include more than 16,383 characters. +|=== + === New features [cols="2", options="header"] diff --git a/modules/ROOT/pages/patterns/reference.adoc b/modules/ROOT/pages/patterns/reference.adoc index 4ea43aaab..3f762c53f 100644 --- a/modules/ROOT/pages/patterns/reference.adoc +++ b/modules/ROOT/pages/patterns/reference.adoc @@ -1165,10 +1165,10 @@ anyPathSearch ::= "ANY" [ numberOfPaths ] [ pathOrPaths ] pathOrPaths ::= { "PATH" | "PATHS" } -numberOfPaths ::= unsignedDecimalInteger +numberOfPaths ::= unsignedDecimalInteger | parameter -numberOfGroups ::= unsignedDecimalInteger +numberOfGroups ::= unsignedDecimalInteger | parameter ---- [NOTE] diff --git a/modules/ROOT/pages/patterns/shortest-paths.adoc b/modules/ROOT/pages/patterns/shortest-paths.adoc index a177ffce3..51c7f4869 100644 --- a/modules/ROOT/pages/patterns/shortest-paths.adoc +++ b/modules/ROOT/pages/patterns/shortest-paths.adoc @@ -19,7 +19,7 @@ Use Cypher if: * You need to specify complex graph navigation via xref:patterns/variable-length-patterns.adoc#quantified-path-patterns[quantified path patterns]. * Creating a link:https://neo4j.com/docs/graph-data-science/current/management-ops/graph-creation/graph-project/[graph projection] takes too long. -* GDS is not available in your instance, or the size of the GDS projection is too large for your instance. +* GDS is not available in your instance, or the size of the GDS projection is too large for your instance. Use GDS if: @@ -64,7 +64,7 @@ CREATE (asc)-[:LINK {distance: 7.25}]->(cnm), (wof)-[:LINK {distance: 0.65}]->(wos) ---- -The paths matched by a xref:patterns/fixed-length-patterns.adoc#path-patterns[path pattern] can be restricted to only the shortest (by number of hops) by including the keyword `SHORTEST k`, where `k` is the number of paths to match. +The paths matched by a xref:patterns/fixed-length-patterns.adoc#path-patterns[path pattern] can be restricted to only the shortest (by number of hops) by including the keyword `SHORTEST k`, where `k` is the number of paths to match, and can be either an `INTEGER` literal or a parameter which resolves to an `INTEGER`. For example, the following example uses `SHORTEST 1` to return the length of the shortest path between `Worcester Shrub Hill` and `Bromsgrove`: .Query diff --git a/modules/ROOT/pages/subqueries/call-subquery.adoc b/modules/ROOT/pages/subqueries/call-subquery.adoc index 6f8a4905a..c96e6cfee 100644 --- a/modules/ROOT/pages/subqueries/call-subquery.adoc +++ b/modules/ROOT/pages/subqueries/call-subquery.adoc @@ -30,14 +30,9 @@ CREATE (teamA:Team {name: 'Team A'}), (playerF:Player {name: 'Player F', age: 35}), (playerA)-[:PLAYS_FOR]->(teamA), (playerB)-[:PLAYS_FOR]->(teamA), - (playerC)-[:PLAYS_FOR]->(teamA), (playerD)-[:PLAYS_FOR]->(teamB), (playerE)-[:PLAYS_FOR]->(teamC), (playerF)-[:PLAYS_FOR]->(teamC), - (playerA)-[:FRIEND_OF]->(playerB), - (playerA)-[:FRIEND_OF]->(playerC), - (playerB)-[:FRIEND_OF]->(playerF), - (playerC)-[:FRIEND_OF]->(playerD), (teamA)-[:OWES {dollars: 1500}]->(teamB), (teamA)-[:OWES {dollars: 3000}]->(teamB), (teamB)-[:OWES {dollars: 1700}]->(teamC), @@ -92,7 +87,6 @@ CALL () { SET p.age = p.age + 1 RETURN p.age AS newAge } -WITH x, newAge MATCH (p:Player {name: 'Player A'}) RETURN x AS iteration, newAge, p.age AS totalAge ---- @@ -139,7 +133,7 @@ RETURN t AS team, players | players | (:Team {name: "Team A"}) -| [(:Player {name: "Player C", age: 19}), (:Player {name: "Player B", age: 23}), (:Player {name: "Player A", age: 24})] +| (:Player {name: "Player B", age: 23}), (:Player {name: "Player A", age: 24})] | (:Team {name: "Team B"}) | [(:Player {name: "Player D", age: 30})] @@ -432,60 +426,60 @@ Similar to xref:clauses/optional-match.adoc[`OPTIONAL MATCH`] any empty rows pro .Difference between using `CALL` and `OPTIONAL CALL` ==== -This example, which finds the friends of each `Player` and xref:functions/aggregating.adoc#functions-count[counts] the number of friends per player, highlights the difference between using `CALL` and `OPTIONAL CALL`. +This example, which finds the team that each `Player` plays for, highlights the difference between using `CALL` and `OPTIONAL CALL`. .Regular subquery `CALL` [source, cypher] ---- MATCH (p:Player) CALL (p) { - MATCH (p)-[:FRIEND_OF]->(friend:Player) - RETURN friend + MATCH (p)-[:PLAYS_FOR]->(team:Team) + RETURN team } -RETURN p.name AS playerName, count(friend) AS numberOfFriends -ORDER BY numberOfFriends +RETURN p.name AS playerName, team.name AS team ---- -.Optional subquery `CALL` +.Result [role="queryresult",options="header,footer",cols="2*m"] |=== -| playerName | numberOfFriends +| playerName | team -| "Player B" | 1 -| "Player C" | 1 -| "Player A" | 2 +| "Player A" | "Team A" +| "Player B" | "Team A" +| "Player D" | "Team B" +| "Player E" | "Team C" +| "Player F" | "Team C" -2+d|Rows: 3 +2+d|Rows: 5 |=== -Note that no results are returned for `Player D`, `Player E`, and `Player F`, since they have no outgoing `FRIEND_OF` relationships connected to them. +Note that no results are returned for `Player C`, since they are not connected to any `Team` with a `PLAYS_FOR` relationship. .Query using regular `OPTIONAL CALL` [source, cypher] ---- MATCH (p:Player) OPTIONAL CALL (p) { - MATCH (p)-[:FRIEND_OF]->(friend:Player) - RETURN friend + MATCH (p)-[:PLAYS_FOR]->(team:Team) + RETURN team } -RETURN p.name AS playerName, count(friend) AS numberOfFriends -ORDER BY numberOfFriends +RETURN p.name AS playerName, team.name AS team ---- -Now, all `Player` nodes, regardless of whether they have any friends or not, are returned. -(Those without any outgoing `FRIEND_OF` relationships are returned with the result `0` because `count()` ignores `null` values.) +Now all `Player` nodes, regardless of whether they have any `PLAYS_FOR` relationships connected to a `Team`, are returned. +.Result .Result [role="queryresult",options="header,footer",cols="2*m"] |=== -| playerName | numberOfFriends +| playerName | team -| "Player D" | 0 -| "Player E" | 0 -| "Player F" | 0 -| "Player B" | 1 -| "Player C" | 1 -| "Player A" | 2 +| "Player A" | "Team A" +| "Player B" | "Team A" +| "Player C" | NULL +| "Player D" | "Team B" +| "Player E" | "Team C" +| "Player F" | "Team C" 2+d|Rows: 6 |=== @@ -495,8 +489,8 @@ Now, all `Player` nodes, regardless of whether they have any friends or not, are [[call-execution-order]] == Execution order of CALL subqueries -The order in which subqueries are executed is not defined. -If a query result depends on the order of execution of subqueries, an `ORDER BY` clause should precede the `CALL` clause. +The order in which rows from the outer scope are passed into subqueries is not defined. +If the results of the subquery depend on the order of these rows, use an `ORDER BY` clause before the `CALL` clause to guarantee a specific processing order for the rows. .Ordering results before `CALL` subquery ==== @@ -590,13 +584,13 @@ UNION ORDER BY p.age DESC LIMIT 1 } -RETURN p.name AS name, p.age AS age +RETURN p.name AS playerName, p.age AS age ---- .Result [role="queryresult",options="header,footer",cols="2*(p2:Player) - RETURN p2.name AS friend + MATCH (p)-[:PLAYS_FOR]->(team:Team) + RETURN team.name AS team } -RETURN p.name AS player, friend +RETURN p.name AS playerName, team ---- .Result -[role="queryresult",options="header,footer",cols="2*= 0.6" @@ -933,9 +933,9 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, "dependencies": { "accepts": "~1.3.8", @@ -943,7 +943,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -3110,9 +3110,9 @@ } }, "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true }, "cookie-signature": { @@ -3234,9 +3234,9 @@ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, "requires": { "accepts": "~1.3.8", @@ -3244,7 +3244,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", diff --git a/package.json b/package.json index 6fa63e771..527fcd494 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "asciidoctor-kroki": "^0.18.1" }, "devDependencies": { - "express": "^4.21.0", + "express": "^4.21.1", "nodemon": "^3.1.7" }, "overrides": {