|
25 | 25 |
|
26 | 26 | import java.util.ArrayList;
|
27 | 27 | import java.util.Collection;
|
28 |
| -import java.util.Collections; |
| 28 | +import java.util.HashSet; |
29 | 29 | import java.util.List;
|
| 30 | +import java.util.Set; |
30 | 31 | import java.util.function.Predicate;
|
31 | 32 | import java.util.function.UnaryOperator;
|
32 | 33 |
|
|
37 | 38 | import org.neo4j.cypherdsl.core.Expression;
|
38 | 39 | import org.neo4j.cypherdsl.core.Functions;
|
39 | 40 | import org.neo4j.cypherdsl.core.MapProjection;
|
| 41 | +import org.neo4j.cypherdsl.core.NamedPath; |
40 | 42 | import org.neo4j.cypherdsl.core.Node;
|
41 | 43 | import org.neo4j.cypherdsl.core.Parameter;
|
42 | 44 | import org.neo4j.cypherdsl.core.Relationship;
|
@@ -70,8 +72,6 @@ public enum CypherGenerator {
|
70 | 72 |
|
71 | 73 | private static final SymbolicName RELATIONSHIP_NAME = Cypher.name("relProps");
|
72 | 74 |
|
73 |
| - private static final int RELATIONSHIP_DEPTH_LIMIT = 2; |
74 |
| - |
75 | 75 | /**
|
76 | 76 | * @param nodeDescription The node description for which a match clause should be generated
|
77 | 77 | * @return An ongoing match
|
@@ -327,28 +327,101 @@ public Expression createReturnStatementForMatch(NodeDescription<?> nodeDescripti
|
327 | 327 | Predicate<String> includeField = s -> inputProperties == null || inputProperties.isEmpty()
|
328 | 328 | || inputProperties.contains(s);
|
329 | 329 |
|
| 330 | + SymbolicName nodeName = Constants.NAME_OF_ROOT_NODE; |
330 | 331 | List<RelationshipDescription> processedRelationships = new ArrayList<>();
|
| 332 | + boolean containsPossibleCircles = containsPossibleCircles(nodeDescription); |
331 | 333 |
|
332 |
| - return projectPropertiesAndRelationships(nodeDescription, Constants.NAME_OF_ROOT_NODE, includeField, |
333 |
| - processedRelationships); |
| 334 | + return projectPropertiesAndRelationships(nodeDescription, nodeName, includeField, |
| 335 | + processedRelationships, containsPossibleCircles); |
334 | 336 | }
|
335 | 337 |
|
| 338 | + // recursive entry point for relationships in return statement |
336 | 339 | private MapProjection projectAllPropertiesAndRelationships(NodeDescription<?> nodeDescription, SymbolicName nodeName,
|
337 | 340 | List<RelationshipDescription> processedRelationships) {
|
338 | 341 |
|
339 | 342 | 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); |
341 | 346 | }
|
342 | 347 |
|
343 | 348 | 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 | + } |
345 | 366 |
|
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(); |
350 | 369 |
|
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 | + } |
352 | 425 | }
|
353 | 426 |
|
354 | 427 | /**
|
@@ -400,10 +473,6 @@ private List<Object> generateListsFor(Collection<RelationshipDescription> relati
|
400 | 473 | continue;
|
401 | 474 | }
|
402 | 475 |
|
403 |
| - if (Collections.frequency(processedRelationships, relationshipDescription) > RELATIONSHIP_DEPTH_LIMIT) { |
404 |
| - return mapProjectionLists; |
405 |
| - } |
406 |
| - |
407 | 476 | generateListFor(relationshipDescription, nodeName, processedRelationships, fieldName, mapProjectionLists);
|
408 | 477 | }
|
409 | 478 |
|
|
0 commit comments