Skip to content

Commit 930f8fc

Browse files
committed
DATAGRAPH-1392 - Optimize loading of cyclic data.
Currently the data for a model that forms a cycle containing same node labels will be cut off on a certain level. This commit changes the query creation and mapping to make use of the path functionality in Neo4j.
1 parent 17a9235 commit 930f8fc

File tree

8 files changed

+202
-171
lines changed

8 files changed

+202
-171
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
<cdi>2.0</cdi>
8080
<changelist>-SNAPSHOT</changelist>
8181
<checkstyle.version>8.29</checkstyle.version>
82-
<cypher-dsl.version>2020.1.0</cypher-dsl.version>
82+
<cypher-dsl.version>2020.1.1</cypher-dsl.version>
8383
<dist.id>spring-data-neo4j</dist.id>
8484
<dist.key>SDNEO4J</dist.key>
8585
<flatten-maven-plugin.version>1.2.1</flatten-maven-plugin.version>

src/main/java/org/springframework/data/neo4j/core/mapping/Constants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public final class Constants {
4040
public static final String NAME_OF_PROPERTIES_PARAM = "__properties__";
4141
public static final String NAME_OF_STATIC_LABELS_PARAM = "__staticLabels__";
4242
public static final String NAME_OF_ENTITY_LIST_PARAM = "__entities__";
43+
public static final String NAME_OF_PATHS = "__paths__";
4344

4445
public static final String FROM_ID_PARAMETER_NAME = "fromId";
4546

src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525

2626
import java.util.ArrayList;
2727
import java.util.Collection;
28-
import java.util.Collections;
28+
import java.util.HashSet;
2929
import java.util.List;
30+
import java.util.Set;
3031
import java.util.function.Predicate;
3132
import java.util.function.UnaryOperator;
3233

@@ -37,6 +38,7 @@
3738
import org.neo4j.cypherdsl.core.Expression;
3839
import org.neo4j.cypherdsl.core.Functions;
3940
import org.neo4j.cypherdsl.core.MapProjection;
41+
import org.neo4j.cypherdsl.core.NamedPath;
4042
import org.neo4j.cypherdsl.core.Node;
4143
import org.neo4j.cypherdsl.core.Parameter;
4244
import org.neo4j.cypherdsl.core.Relationship;
@@ -70,8 +72,6 @@ public enum CypherGenerator {
7072

7173
private static final SymbolicName RELATIONSHIP_NAME = Cypher.name("relProps");
7274

73-
private static final int RELATIONSHIP_DEPTH_LIMIT = 2;
74-
7575
/**
7676
* @param nodeDescription The node description for which a match clause should be generated
7777
* @return An ongoing match
@@ -327,28 +327,101 @@ public Expression createReturnStatementForMatch(NodeDescription<?> nodeDescripti
327327
Predicate<String> includeField = s -> inputProperties == null || inputProperties.isEmpty()
328328
|| inputProperties.contains(s);
329329

330+
SymbolicName nodeName = Constants.NAME_OF_ROOT_NODE;
330331
List<RelationshipDescription> processedRelationships = new ArrayList<>();
332+
boolean containsPossibleCircles = containsPossibleCircles(nodeDescription);
331333

332-
return projectPropertiesAndRelationships(nodeDescription, Constants.NAME_OF_ROOT_NODE, includeField,
333-
processedRelationships);
334+
return projectPropertiesAndRelationships(nodeDescription, nodeName, includeField,
335+
processedRelationships, containsPossibleCircles);
334336
}
335337

338+
// recursive entry point for relationships in return statement
336339
private MapProjection projectAllPropertiesAndRelationships(NodeDescription<?> nodeDescription, SymbolicName nodeName,
337340
List<RelationshipDescription> processedRelationships) {
338341

339342
Predicate<String> includeAllFields = (field) -> true;
340-
return projectPropertiesAndRelationships(nodeDescription, nodeName, includeAllFields, processedRelationships);
343+
// Because we are getting called recursive, there cannot be any circle
344+
return projectPropertiesAndRelationships(nodeDescription, nodeName, includeAllFields, processedRelationships,
345+
false);
341346
}
342347

343348
private MapProjection projectPropertiesAndRelationships(NodeDescription<?> nodeDescription, SymbolicName nodeName,
344-
Predicate<String> includeProperty, List<RelationshipDescription> processedRelationships) {
349+
Predicate<String> includeProperty, List<RelationshipDescription> processedRelationships,
350+
boolean containsPossibleCircles) {
351+
352+
List<Object> propertiesProjection = projectNodeProperties(nodeDescription, nodeName, includeProperty);
353+
List<Object> contentOfProjection = new ArrayList<>(propertiesProjection);
354+
if (containsPossibleCircles) {
355+
Relationship pattern = anyNode(nodeName).relationshipBetween(anyNode(),
356+
collectAllRelationshipTypes(nodeDescription)).unbounded();
357+
NamedPath p = Cypher.path("p").definedBy(pattern);
358+
contentOfProjection.add(Constants.NAME_OF_PATHS);
359+
contentOfProjection.add(Cypher.listBasedOn(p).returning(p));
360+
} else {
361+
contentOfProjection.addAll(
362+
generateListsFor(nodeDescription.getRelationships(), nodeName, includeProperty, processedRelationships));
363+
}
364+
return Cypher.anyNode(nodeName).project(contentOfProjection);
365+
}
345366

346-
List<Object> contentOfProjection = new ArrayList<>();
347-
contentOfProjection.addAll(projectNodeProperties(nodeDescription, nodeName, includeProperty));
348-
contentOfProjection.addAll(
349-
generateListsFor(nodeDescription.getRelationships(), nodeName, includeProperty, processedRelationships));
367+
private boolean containsPossibleCircles(NodeDescription<?> nodeDescription) {
368+
Collection<RelationshipDescription> relationships = nodeDescription.getRelationships();
350369

351-
return Cypher.anyNode(nodeName).project(contentOfProjection);
370+
Set<RelationshipDescription> processedRelationships = new HashSet<>();
371+
for (RelationshipDescription relationship : relationships) {
372+
if (processedRelationships.contains(relationship)) {
373+
return true;
374+
}
375+
processedRelationships.add(relationship);
376+
if (containsPossibleCircles(relationship.getTarget(), processedRelationships)) {
377+
return true;
378+
}
379+
}
380+
return false;
381+
}
382+
383+
private boolean containsPossibleCircles(NodeDescription<?> nodeDescription, Set<RelationshipDescription> processedRelationships) {
384+
Collection<RelationshipDescription> relationships = nodeDescription.getRelationships();
385+
386+
for (RelationshipDescription relationship : relationships) {
387+
if (processedRelationships.contains(relationship)) {
388+
return true;
389+
}
390+
processedRelationships.add(relationship);
391+
if (containsPossibleCircles(relationship.getTarget(), processedRelationships)) {
392+
return true;
393+
}
394+
}
395+
return false;
396+
}
397+
398+
private String[] collectAllRelationshipTypes(NodeDescription<?> nodeDescription) {
399+
Set<String> relationshipTypes = new HashSet<>();
400+
401+
for (RelationshipDescription relationshipDescription : nodeDescription.getRelationships()) {
402+
String relationshipType = relationshipDescription.getType();
403+
if (relationshipTypes.contains(relationshipType)) {
404+
break;
405+
}
406+
if (relationshipDescription.isDynamic()) {
407+
relationshipTypes.clear();
408+
break;
409+
}
410+
relationshipTypes.add(relationshipType);
411+
collectAllRelationshipTypes(relationshipDescription.getTarget(), relationshipTypes);
412+
}
413+
return relationshipTypes.toArray(new String[0]);
414+
}
415+
416+
private void collectAllRelationshipTypes(NodeDescription<?> nodeDescription, Set<String> processedRelationshipTypes) {
417+
418+
for (RelationshipDescription relationshipDescription : nodeDescription.getRelationships()) {
419+
String relationshipType = relationshipDescription.getType();
420+
if (processedRelationshipTypes.contains(relationshipType)) {
421+
continue;
422+
}
423+
processedRelationshipTypes.add(relationshipType);
424+
}
352425
}
353426

354427
/**
@@ -400,10 +473,6 @@ private List<Object> generateListsFor(Collection<RelationshipDescription> relati
400473
continue;
401474
}
402475

403-
if (Collections.frequency(processedRelationships, relationshipDescription) > RELATIONSHIP_DEPTH_LIMIT) {
404-
return mapProjectionLists;
405-
}
406-
407476
generateListFor(relationshipDescription, nodeName, processedRelationships, fieldName, mapProjectionLists);
408477
}
409478

0 commit comments

Comments
 (0)