Skip to content

Commit 8e075c4

Browse files
authored
Merge pull request #7364 from FlorentinD/document-cypher-agg-label-properties-behaviour-23
Improve cypher aggregation docs
2 parents 1fa79d3 + 861ac3f commit 8e075c4

File tree

4 files changed

+103
-23
lines changed

4 files changed

+103
-23
lines changed

doc-test-tools/src/main/java/org/neo4j/gds/doc/syntax/ProcedureSyntaxAutoChecker.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ private Consumer<StructuralNode> assertTableValues(
166166
.getCells()
167167
.get(0)) // Get the first column in the row --> corresponds to the return column names
168168
.map(Cell::getText)
169+
// remove any potential links in the names
170+
.map(name -> name.replaceAll("<a.*\">|<\\/a>", ""))
169171
// as java identifier cannot contain white spaces, remove anything after the first space such as footnote:
170172
.map(name -> name.split("\\s+")[0])
171173
.collect(Collectors.toList());
@@ -176,7 +178,7 @@ private Consumer<StructuralNode> assertTableValues(
176178
};
177179
}
178180

179-
private Iterable<String> extractDocResultFields(String syntaxCode) {
181+
private static Iterable<String> extractDocResultFields(String syntaxCode) {
180182
var yield = syntaxCode.substring(syntaxCode.indexOf(YIELD_KEYWORD) + YIELD_KEYWORD.length()).trim();
181183
return Arrays.stream(yield.split(YIELD_FIELD_SEPARATOR))
182184
.map(yieldField -> yieldField.split(YIELD_NAME_DATA_TYPE_SEPARATOR)[0].trim())

doc-test-tools/src/test/java/org/neo4j/gds/doc/syntax/ProcedureSyntaxAutoCheckerTest.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@
2323
import org.asciidoctor.OptionsBuilder;
2424
import org.asciidoctor.SafeMode;
2525
import org.assertj.core.api.SoftAssertions;
26+
import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
2627
import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
2728
import org.junit.jupiter.api.BeforeEach;
2829
import org.junit.jupiter.api.Test;
2930
import org.junit.jupiter.api.extension.ExtendWith;
3031
import org.junit.jupiter.api.io.TempDir;
32+
import org.junit.jupiter.params.ParameterizedTest;
33+
import org.junit.jupiter.params.provider.ValueSource;
3134

3235
import java.io.File;
3336
import java.net.URISyntaxException;
@@ -48,6 +51,9 @@ class ProcedureSyntaxAutoCheckerTest {
4851

4952
private static final String newLine = System.lineSeparator();
5053

54+
@InjectSoftAssertions
55+
private SoftAssertions softAssertions;
56+
5157
@BeforeEach
5258
void setUp() {
5359
// By default we are forced to use relative path which we don't want.
@@ -57,18 +63,19 @@ void setUp() {
5763

5864
}
5965

60-
@Test
61-
void correctSyntaxSectionTest(SoftAssertions softAssertions) throws URISyntaxException {
66+
@ParameterizedTest(name = "{0}")
67+
@ValueSource(strings = {"include-with-syntax.adoc", "include-with-syntax-parameter-with-link.adoc"})
68+
void correctSyntaxSectionTest(String positiveResource) throws URISyntaxException {
6269
try (var asciidoctor = createAsciidoctor(softAssertions)) {
63-
var file = Paths.get(getClass().getClassLoader().getResource("include-with-syntax.adoc").toURI()).toFile();
70+
var file = Paths.get(getClass().getClassLoader().getResource(positiveResource).toURI()).toFile();
6471
assertTrue(file.exists() && file.canRead());
6572

6673
asciidoctor.convertFile(file, options);
6774
}
6875
}
6976

7077
@Test
71-
void shouldFailOnMissingResultsTable(SoftAssertions softAssertions) throws URISyntaxException {
78+
void shouldFailOnMissingResultsTable() throws URISyntaxException {
7279
try (var asciidoctor = createAsciidoctor(softAssertions)) {
7380
var file = Paths
7481
.get(getClass()
@@ -85,7 +92,7 @@ void shouldFailOnMissingResultsTable(SoftAssertions softAssertions) throws URISy
8592
}
8693

8794
@Test
88-
void shouldFailOnMoreThanOneResultsTable(SoftAssertions softAssertions) throws URISyntaxException {
95+
void shouldFailOnMoreThanOneResultsTable() throws URISyntaxException {
8996
try (var asciidoctor = createAsciidoctor(softAssertions)) {
9097
var file = Paths
9198
.get(getClass()
@@ -102,7 +109,7 @@ void shouldFailOnMoreThanOneResultsTable(SoftAssertions softAssertions) throws U
102109
}
103110

104111
@Test
105-
void shouldFailOnMissingCodeBlock(SoftAssertions softAssertions) throws URISyntaxException {
112+
void shouldFailOnMissingCodeBlock() throws URISyntaxException {
106113
try (var asciidoctor = createAsciidoctor(softAssertions)) {
107114
var file = Paths
108115
.get(getClass().getClassLoader().getResource("invalid-include-with-syntax-no-code-block.adoc").toURI())
@@ -116,7 +123,7 @@ void shouldFailOnMissingCodeBlock(SoftAssertions softAssertions) throws URISynta
116123
}
117124

118125
@Test
119-
void shouldFailOnMoreThanOneCodeBlock(SoftAssertions softAssertions) throws URISyntaxException {
126+
void shouldFailOnMoreThanOneCodeBlock() throws URISyntaxException {
120127
try (var asciidoctor = createAsciidoctor(softAssertions)) {
121128
var file = Paths
122129
.get(getClass()
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[.include-with-stream]
2+
======
3+
.Run Louvain in stream mode on a named graph.
4+
[source, cypher, role=noplay]
5+
----
6+
CALL gds.louvain.stream(
7+
graphName: String,
8+
configuration: Map
9+
)
10+
YIELD
11+
nodeId: Integer,
12+
communityId: Integer,
13+
intermediateCommunityIds: List of Integer
14+
----
15+
16+
.Parameters
17+
[opts="header"]
18+
|===
19+
| Name
20+
| graphName footnote:vital[choose the name wisely]
21+
| <<configuration-table, configuration>>
22+
|===
23+
24+
// This table is only here to make sure we will really pick the `.Results` one
25+
[[configuration-table]]
26+
.Algorithm specific configuration
27+
[opts="header",cols="1,1,1m,1,4"]
28+
|===
29+
| Name | Type | Default | Optional | Description
30+
| relationshipWeightProperty | String | null | yes | Relationship Weight.
31+
| seedProperty | String | n/a | yes | Seed Property.
32+
|===
33+
34+
.Results
35+
[opts="header",cols="1,1,6"]
36+
|===
37+
| Name | Type | Description
38+
| nodeId | Integer | Node ID.
39+
| communityId | Integer | The community ID of the final level.
40+
| intermediateCommunityIds | List of Integer | Community IDs for each level. `Null` if `includeIntermediateCommunities` is set to false.
41+
|===
42+
======

doc/modules/ROOT/pages/management-ops/projections/graph-project-cypher-aggregation.adoc

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,44 @@
33
:description: This section details projecting GDS graphs using `Cypher` aggregations.
44

55

6+
Using Cypher aggregations is a more flexible and expressive approach with diminished focus on performance compared to the xref:management-ops/projections/graph-project.adoc[native projections].
7+
Cypher aggregations are primarily recommended for the development phase (see xref:common-usage/index.adoc[Common usage]).
68

7-
A projected graph can be stored in the catalog under a user-defined name.
8-
Using that name, the graph can be referred to by any algorithm in the library.
9-
This allows multiple algorithms to use the same graph without having to project it on each algorithm run.
109

11-
Using Cypher aggregations is a more flexible and expressive approach with diminished focus on performance compared to the xref:management-ops/projections/graph-project.adoc[native projections].
12-
Cypher projections are primarily recommended for the development phase (see xref:common-usage/index.adoc[Common usage]).
10+
== Considerations
1311

14-
[NOTE]
15-
--
16-
There is also a way to generate a random graph, see xref:management-ops/projections/graph-generation.adoc[Graph Generation] documentation for more details.
17-
--
12+
=== Lifecycle
1813

1914
[NOTE]
2015
--
21-
The projected graph will reside in the catalog until:
16+
The projected graphs will reside in the catalog until either:
2217

2318
- the graph is dropped using xref:graph-drop.adoc[gds.graph.drop]
2419
- the Neo4j database from which the graph was projected is stopped or dropped
2520
- the Neo4j database management system is stopped.
2621
--
2722

2823

24+
=== Node property support
25+
26+
Cypher aggregations can only project a limited set of node property types from a Cypher query.
27+
The xref:management-ops/node-properties.adoc#node-properties-supported[Node Properties page] details which node property types are supported.
28+
Other types of node properties have to be transformed or encoded into one of the supported types in order to be projected using a Cypher aggregation.
29+
30+
=== Selection of node properties and labels
31+
32+
If a node occurs multiple times, the node properties and labels of the first occurrence will be used for the projection.
33+
This is important when a node can be a source node as well as a target node and their configuration differs.
34+
Relevant configuration options are `sourceNodeProperties`, `targetNodeProperties`, `sourceNodeLabels` and `targetNodeLabels`.
35+
36+
2937
[[graph-project-cypher-aggregation-syntax]]
3038
== Syntax
3139

3240
A Cypher aggregation is used in a query as an aggregation over the relationships that are being projected.
3341
It takes three mandatory arguments: `graphName`, `sourceNode` and `targetNode`.
34-
In addition, the optional `sourceNodeProperties`, `targetNodeProperties`, and `relationshipProperties` parameters allows us to project properties.
42+
In addition, two optional parameters can be used to project node properties and labels (`nodesConfig`) or relationship properties and type (`relationshipConfig`).
43+
The optional `configuration` parameter can be used for general configuration of the projection such as `readConcurrency`.
3544

3645
[.graph-project-cypher-aggregation-syntax]
3746
--
@@ -53,15 +62,35 @@ RETURN gds.alpha.graph.project(
5362
----
5463

5564
.Parameters
56-
[opts="header",cols="1,1,8"]
65+
[opts="header",cols="2,1,7"]
5766
|===
5867
| Name | Optional | Description
5968
| graphName | no | The name under which the graph is stored in the catalog.
6069
| sourceNode | no | The source node of the relationship. Must not be null.
6170
| targetNode | yes | The target node of the relationship. The targetNode can be null (for example due to an `OPTIONAL MATCH`), in which case the source node is projected as an unconnected node.
62-
| nodesConfig | yes | Properties and Labels configuration for the source and target nodes.
63-
| relationshipConfig | yes | Properties and Type configuration for the relationship.
64-
| configuration | yes | Additional parameters to configure the cypher aggregation projection.
71+
| <<graph-project-cypher-aggregation-syntax-nodesConfig, nodesConfig>> | yes | Properties and labels configuration for the source and target nodes.
72+
| <<graph-project-cypher-aggregation-syntax-relationshipConfig, relationshipConfig>> | yes | Properties and type configuration for the relationship.
73+
| <<graph-project-cypher-aggregation-syntax-configuration, configuration>> | yes | Additional parameters to configure the projection.
74+
|===
75+
76+
[[graph-project-cypher-aggregation-syntax-nodesConfig]]
77+
.Nodes configuration
78+
[opts="header",cols="1,1,1,4"]
79+
|===
80+
| Name | Type | Default | Description
81+
| sourceNodeProperties | Map | {} | The properties of the source node.
82+
| targetNodeProperties | Map | {} | The properties of the target node.
83+
| sourceNodeLabels | List of String or String | [] | The label(s) of the source node.
84+
| targetNodeLabels | List of String or String | [] | The label(s) of the source node.
85+
|===
86+
87+
[[graph-project-cypher-aggregation-syntax-relationshipConfig]]
88+
.Relationship configuration
89+
[opts="header",cols="1,1,1,4"]
90+
|===
91+
| Name | Type | Default | Description
92+
| properties | Map | {} | The properties of the source node.
93+
| relationshipType | String | '*' | The type of the relationship.
6594
|===
6695

6796
[[graph-project-cypher-aggregation-syntax-configuration]]

0 commit comments

Comments
 (0)