From 05b0b374e765f58b5e9d10b0acc6bd94d187e54c Mon Sep 17 00:00:00 2001 From: Richard Sill Date: Tue, 9 Dec 2025 17:21:52 +0100 Subject: [PATCH 1/6] added performance caveats table entries and an additions entry --- modules/ROOT/pages/clauses/match.adoc | 12 ++++++++++-- modules/ROOT/pages/clauses/merge.adoc | 8 ++++++++ ...tions-additions-removals-compatibility.adoc | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/clauses/match.adoc b/modules/ROOT/pages/clauses/match.adoc index 6304b38e3..c01674a03 100644 --- a/modules/ROOT/pages/clauses/match.adoc +++ b/modules/ROOT/pages/clauses/match.adoc @@ -649,15 +649,23 @@ The table below outlines performance caveats for specific Neo4j versions. | Neo4j versions | Performance caveat | 5.26 -- 2025.07 -| The xref:planning-and-tuning/execution-plans.adoc[Cypher planner] is not able to leverage xref:indexes/search-performance-indexes/index.adoc[indexes] with xref:planning-and-tuning/operators/operators-detail.adoc#leaf-operators[index scans or seeks] and must instead utilize the xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-all-nodes-scan[`AllNodesScan`] operator, which reads all nodes from the node store and is therefore more costly. +| The xref:planning-and-tuning/execution-plans.adoc[Cypher planner] is not able to leverage xref:indexes/search-performance-indexes/index.adoc[indexes] with xref:planning-and-tuning/operators/operators-detail.adoc#leaf-operators[index scans or seeks] and must instead utilize the xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-all-nodes-scan[`AllNodesScan`] operator, which reads all nodes from the node store and is therefore more costly. -| 2025.08 -- current +| 2025.08 -- 2025.10 | The Cypher planner is able to leverage xref:indexes/search-performance-indexes/using-indexes.adoc#token-lookup-indexes[token lookup indexes] when matching node labels and relationship types dynamically. This is enabled by the introduction of three new query plan operators: xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-dynamic-label-node-lookup[`DynamicLabelNodeLookup`], xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-dynamic-directed-relationship-type-lookup[`DynamicDirectedRelationshipTypeLookup`], and xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-dynamic-undirected-relationship-type-lookup[`DynamicUndirectedRelationshipTypeLookup`]. It is not, however, able to use indexes on property values. For example, `MATCH (n:$(Label) {foo: bar})` will not use any indexes on `n.foo` but can use a `DynamicLabelNodeLookup` on `$(label)`. +| 2025.11 -- current +a| The Cypher planner is able to leverage indexes on property values, however: + +. It only supports exact seeks on range indexes (no full text or spatial). +. The index order cannot be leveraged, so the planner must insert separate ordering if required later on in the query. +. Parallel runtime seeks and scans are single-threaded. +. The planner doesn't combine multiple property index seeks when generating the results for the dynamic part of the query. For example, using `$any` in combination with multiple labels that share an index on a property result in the planner choosing one of the indexes based on selectivity and then stepping through the seek results and filtering for the remainder of the expression. + |=== [[further-reading]] diff --git a/modules/ROOT/pages/clauses/merge.adoc b/modules/ROOT/pages/clauses/merge.adoc index 6e20bd3f6..f9c86d9d7 100644 --- a/modules/ROOT/pages/clauses/merge.adoc +++ b/modules/ROOT/pages/clauses/merge.adoc @@ -759,4 +759,12 @@ xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-dynamic-labe It is not, however, able to use indexes on property values. For example, `MERGE (n:$(Label) {foo: bar})` will not use any indexes on `n.foo` but can use a `DynamicLabelNodeLookup` on `$(label)`. +| 2025.11 -- current +a| The Cypher planner is able to leverage indexes on property values, however: + +. It only supports exact seeks on range indexes (no full text or spatial). +. The index order cannot be leveraged, so the planner must insert separate ordering if required later on in the query. +. Parallel runtime seeks and scans are single-threaded. +. The planner doesn't combine multiple property index seeks when generating the results for the dynamic part of the query. For example, using `$any` in combination with multiple labels that share an index on a property result in the planner choosing one of the indexes based on selectivity and then stepping through the seek results and filtering for the remainder of the expression. + |=== \ No newline at end of file diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index ab35de6ee..64daae481 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -59,6 +59,24 @@ a| Both are similar to the `Merge` operator, but: * xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-merge-into[`MergeInto`] is used when the start and end node of the pattern is matched outside the `MERGE` pattern; * xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-merge-unique-node[`MergeUniqueNode`] is used when there is a property uniqueness constraint on the property used in the `MERGE` statement. +a| +label:functionality[] +label:updated[] +[source, cypher] +---- +CREATE RANGE INDEX actor_has_birthyear FOR (a:Actor) ON (a.birthYear) +CREATE RANGE INDEX director_has_birthyear FOR (d:Director) ON (d.birthYear) + +MATCH (p:$all(["Actor", "Director"]) {birthYear: 1983}) RETURN p.name +---- + +a| Cypher can now leverage indexes on property values, improving the performance of xref:clauses/match.adoc#dynamic-match-caveats[`MATCH`] and xref:clauses/merge.adoc#dynamic-merge-caveats[`MERGE`] when doing so, however: + +. It only supports exact seeks on range indexes (no full text or spatial). +. The index order cannot be leveraged, so the planner must insert separate ordering if required later on in the query. +. Parallel runtime seeks and scans are single-threaded. +. The planner doesn't combine multiple property index seeks when generating the results for the dynamic part of the query. + |=== From add4631dbf481c23bb88ad2090fc1ca79d9f30bb Mon Sep 17 00:00:00 2001 From: Richard Sill Date: Tue, 9 Dec 2025 17:23:20 +0100 Subject: [PATCH 2/6] version range in table --- modules/ROOT/pages/clauses/merge.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/clauses/merge.adoc b/modules/ROOT/pages/clauses/merge.adoc index f9c86d9d7..19aa6f590 100644 --- a/modules/ROOT/pages/clauses/merge.adoc +++ b/modules/ROOT/pages/clauses/merge.adoc @@ -750,9 +750,9 @@ The table below outlines performance caveats for specific Neo4j versions. | Neo4j versions | Performance caveat | 5.26 -- 2025.07 -| The xref:planning-and-tuning/execution-plans.adoc[Cypher planner] is not able to leverage xref:indexes/search-performance-indexes/index.adoc[indexes] with xref:planning-and-tuning/operators/operators-detail.adoc#leaf-operators[index scans or seeks] and must instead utilize the xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-all-nodes-scan[`AllNodesScan`] operator, which reads all nodes from the node store and is therefore more costly. +| The xref:planning-and-tuning/execution-plans.adoc[Cypher planner] is not able to leverage xref:indexes/search-performance-indexes/index.adoc[indexes] with xref:planning-and-tuning/operators/operators-detail.adoc#leaf-operators[index scans or seeks] and must instead utilize the xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-all-nodes-scan[`AllNodesScan`] operator, which reads all nodes from the node store and is therefore more costly. -| 2025.08 -- current +| 2025.08 -- 2025.10 | The Cypher planner is able to leverage xref:indexes/search-performance-indexes/using-indexes.adoc#token-lookup-indexes[token lookup indexes] when matching node labels and relationship types dynamically. This is enabled by the introduction of three new query plan operators: xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-dynamic-label-node-lookup[`DynamicLabelNodeLookup`], xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-dynamic-directed-relationship-type-lookup[`DynamicDirectedRelationshipTypeLookup`], and xref:planning-and-tuning/operators/operators-detail.adoc#query-plan-dynamic-undirected-relationship-type-lookup[`DynamicUndirectedRelationshipTypeLookup`]. From 2761c2a63f896d5f74452bdcc7b04cadf4ce7b4e Mon Sep 17 00:00:00 2001 From: Richard Sill Date: Fri, 12 Dec 2025 16:38:10 +0100 Subject: [PATCH 3/6] review suggestions --- modules/ROOT/pages/clauses/match.adoc | 18 +++++++++++---- modules/ROOT/pages/clauses/merge.adoc | 7 +++--- ...ions-additions-removals-compatibility.adoc | 23 +++++++++++++------ package-lock.json | 12 +++++----- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/modules/ROOT/pages/clauses/match.adoc b/modules/ROOT/pages/clauses/match.adoc index c01674a03..b3a8f3a4c 100644 --- a/modules/ROOT/pages/clauses/match.adoc +++ b/modules/ROOT/pages/clauses/match.adoc @@ -661,10 +661,20 @@ For example, `MATCH (n:$(Label) {foo: bar})` will not use any indexes on `n.foo` | 2025.11 -- current a| The Cypher planner is able to leverage indexes on property values, however: -. It only supports exact seeks on range indexes (no full text or spatial). -. The index order cannot be leveraged, so the planner must insert separate ordering if required later on in the query. -. Parallel runtime seeks and scans are single-threaded. -. The planner doesn't combine multiple property index seeks when generating the results for the dynamic part of the query. For example, using `$any` in combination with multiple labels that share an index on a property result in the planner choosing one of the indexes based on selectivity and then stepping through the seek results and filtering for the remainder of the expression. +* It only supports exact seeks on range indexes (no full text or spatial). +* The index order cannot be leveraged, so the planner must insert separate ordering if required later on in the query. +* Parallel runtime seeks and scans are single-threaded. +* The planner doesn't combine multiple property index seeks when generating the results for the dynamic part of the query. For example, using `$any` in combination with multiple labels that share an index on a property result in the operator choosing one of the indexes based on selectivity and then stepping through the seek results and filtering for the remainder of the expression. ++ Example: ++ +[source, cypher] +---- +CREATE RANGE INDEX actor_has_birthyear FOR (a:Actor) ON (a.birthYear) +CREATE RANGE INDEX director_has_birthyear FOR (d:Director) ON (d.birthYear) + +// The below MATCH can leverage one of the indexes, but not both +MATCH (p:$any(["Actor", "Director"]) { birthYear: 1983 }) RETURN p.name +---- |=== diff --git a/modules/ROOT/pages/clauses/merge.adoc b/modules/ROOT/pages/clauses/merge.adoc index 19aa6f590..3d43b6a4e 100644 --- a/modules/ROOT/pages/clauses/merge.adoc +++ b/modules/ROOT/pages/clauses/merge.adoc @@ -762,9 +762,8 @@ For example, `MERGE (n:$(Label) {foo: bar})` will not use any indexes on `n.foo` | 2025.11 -- current a| The Cypher planner is able to leverage indexes on property values, however: -. It only supports exact seeks on range indexes (no full text or spatial). -. The index order cannot be leveraged, so the planner must insert separate ordering if required later on in the query. -. Parallel runtime seeks and scans are single-threaded. -. The planner doesn't combine multiple property index seeks when generating the results for the dynamic part of the query. For example, using `$any` in combination with multiple labels that share an index on a property result in the planner choosing one of the indexes based on selectivity and then stepping through the seek results and filtering for the remainder of the expression. +* It only supports exact seeks on range indexes (no full text or spatial). +* The index order cannot be leveraged, so the planner must insert separate ordering if required later on in the query. +* Parallel runtime seeks and scans are single-threaded. |=== \ No newline at end of file diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 64daae481..7740a2e89 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -62,20 +62,29 @@ a| Both are similar to the `Merge` operator, but: a| label:functionality[] label:updated[] + +[source, csv] +---- +// people.csv +label,name,birthYear +Actor,Henry Cavill,1983 +---- + [source, cypher] ---- -CREATE RANGE INDEX actor_has_birthyear FOR (a:Actor) ON (a.birthYear) -CREATE RANGE INDEX director_has_birthyear FOR (d:Director) ON (d.birthYear) +CREATE RANGE INDEX actor_has_name_and_birthyear FOR (a:Actor) ON (a.name, a.birthYear); -MATCH (p:$all(["Actor", "Director"]) {birthYear: 1983}) RETURN p.name +LOAD CSV WITH HEADERS FROM 'people.csv' AS row +// The MERGE below can leverage the index to check for existence before a CREATE +MERGE (:$(row.label) { name: row.name, birthYear: row.birthYear) }) ---- a| Cypher can now leverage indexes on property values, improving the performance of xref:clauses/match.adoc#dynamic-match-caveats[`MATCH`] and xref:clauses/merge.adoc#dynamic-merge-caveats[`MERGE`] when doing so, however: -. It only supports exact seeks on range indexes (no full text or spatial). -. The index order cannot be leveraged, so the planner must insert separate ordering if required later on in the query. -. Parallel runtime seeks and scans are single-threaded. -. The planner doesn't combine multiple property index seeks when generating the results for the dynamic part of the query. +* It only supports exact seeks on range indexes (no full text or spatial). +* The index order cannot be leveraged, so the planner must insert separate ordering if required later on in the query. +* Parallel runtime seeks and scans are single-threaded. +* The planner doesn't combine multiple property index seeks when generating the results for the dynamic part of the query. |=== diff --git a/package-lock.json b/package-lock.json index 9f79f9220..5145dbdb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -373,9 +373,9 @@ "license": "MIT" }, "node_modules/@neo4j-antora/roles-labels": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@neo4j-antora/roles-labels/-/roles-labels-0.1.2.tgz", - "integrity": "sha512-ruMZvGRxD3WyFVK/GaAO7gmNnU9zHuzfSbRpCMFJTasv78qRInn1np6WmTHZPbqynBWD3uYkh4pzIutBdJ8WFg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@neo4j-antora/roles-labels/-/roles-labels-0.1.4.tgz", + "integrity": "sha512-qNgUhWaD8zfGQmDwwmpkGTOXg8xnq7hv29Fz0mLV/KEh2+pcqQpPHIPlvCQIOEUlOFu2cp74xev/472A3B3tYA==", "license": "MIT", "dependencies": { "node-html-parser": "^7.0.0" @@ -1718,9 +1718,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", "dev": true, "license": "MIT", "dependencies": { From 2f69eea342e39816789a2387ca474113442f99ec Mon Sep 17 00:00:00 2001 From: Richard Sill Date: Fri, 12 Dec 2025 16:44:39 +0100 Subject: [PATCH 4/6] added test-skip roles --- modules/ROOT/pages/clauses/match.adoc | 2 +- .../pages/deprecations-additions-removals-compatibility.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/clauses/match.adoc b/modules/ROOT/pages/clauses/match.adoc index b3a8f3a4c..e3b162018 100644 --- a/modules/ROOT/pages/clauses/match.adoc +++ b/modules/ROOT/pages/clauses/match.adoc @@ -667,7 +667,7 @@ a| The Cypher planner is able to leverage indexes on property values, however: * The planner doesn't combine multiple property index seeks when generating the results for the dynamic part of the query. For example, using `$any` in combination with multiple labels that share an index on a property result in the operator choosing one of the indexes based on selectivity and then stepping through the seek results and filtering for the remainder of the expression. + Example: + -[source, cypher] +[source, cypher, role=test-skip] ---- CREATE RANGE INDEX actor_has_birthyear FOR (a:Actor) ON (a.birthYear) CREATE RANGE INDEX director_has_birthyear FOR (d:Director) ON (d.birthYear) diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 7740a2e89..64fd9cf12 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -70,7 +70,7 @@ label,name,birthYear Actor,Henry Cavill,1983 ---- -[source, cypher] +[source, cypher, role=test-skip] ---- CREATE RANGE INDEX actor_has_name_and_birthyear FOR (a:Actor) ON (a.name, a.birthYear); From 8e29c2e833c92125bd5f454ed5a0d8abaa37bf00 Mon Sep 17 00:00:00 2001 From: Richard Sill <156673635+rsill-neo4j@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:08:13 +0100 Subject: [PATCH 5/6] Update modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc Co-authored-by: Rob Steward --- .../pages/deprecations-additions-removals-compatibility.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 64fd9cf12..c8c7f17b1 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -76,7 +76,7 @@ CREATE RANGE INDEX actor_has_name_and_birthyear FOR (a:Actor) ON (a.name, a.birt LOAD CSV WITH HEADERS FROM 'people.csv' AS row // The MERGE below can leverage the index to check for existence before a CREATE -MERGE (:$(row.label) { name: row.name, birthYear: row.birthYear) }) +MERGE (:$(row.label) { name: row.name, birthYear: row.birthYear }) ---- a| Cypher can now leverage indexes on property values, improving the performance of xref:clauses/match.adoc#dynamic-match-caveats[`MATCH`] and xref:clauses/merge.adoc#dynamic-merge-caveats[`MERGE`] when doing so, however: From df8e5b7330dd2cc1c4900c6a5d8035fd84b4b194 Mon Sep 17 00:00:00 2001 From: Richard Sill <156673635+rsill-neo4j@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:09:52 +0100 Subject: [PATCH 6/6] Apply suggestions from code review --- .../deprecations-additions-removals-compatibility.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index c8c7f17b1..975ca2a87 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -66,17 +66,17 @@ label:updated[] [source, csv] ---- // people.csv -label,name,birthYear -Actor,Henry Cavill,1983 +label,name +Actor,Henry Cavill ---- [source, cypher, role=test-skip] ---- -CREATE RANGE INDEX actor_has_name_and_birthyear FOR (a:Actor) ON (a.name, a.birthYear); +CREATE RANGE INDEX actor_has_name FOR (a:Actor) ON (a.name); LOAD CSV WITH HEADERS FROM 'people.csv' AS row // The MERGE below can leverage the index to check for existence before a CREATE -MERGE (:$(row.label) { name: row.name, birthYear: row.birthYear }) +MERGE (:$(row.label) { name: row.name }) ---- a| Cypher can now leverage indexes on property values, improving the performance of xref:clauses/match.adoc#dynamic-match-caveats[`MATCH`] and xref:clauses/merge.adoc#dynamic-merge-caveats[`MERGE`] when doing so, however: