25
25
import java .util .ArrayList ;
26
26
import java .util .Arrays ;
27
27
import java .util .Collection ;
28
+ import java .util .Collections ;
28
29
import java .util .HashSet ;
29
30
import java .util .List ;
30
31
import java .util .Set ;
31
32
import java .util .function .Predicate ;
32
33
import java .util .function .UnaryOperator ;
34
+ import java .util .stream .Collectors ;
33
35
34
36
import org .apiguardian .api .API ;
35
37
import org .neo4j .cypherdsl .core .Condition ;
36
38
import org .neo4j .cypherdsl .core .Conditions ;
37
39
import org .neo4j .cypherdsl .core .Cypher ;
38
40
import org .neo4j .cypherdsl .core .Expression ;
41
+ import org .neo4j .cypherdsl .core .FunctionInvocation ;
39
42
import org .neo4j .cypherdsl .core .Functions ;
43
+ import org .neo4j .cypherdsl .core .ListComprehension ;
40
44
import org .neo4j .cypherdsl .core .MapProjection ;
41
45
import org .neo4j .cypherdsl .core .NamedPath ;
42
46
import org .neo4j .cypherdsl .core .Node ;
@@ -100,7 +104,13 @@ public StatementBuilder.OrderableOngoingReadingAndWith prepareMatchOf(NodeDescri
100
104
* @return An ongoing match
101
105
*/
102
106
public StatementBuilder .OrderableOngoingReadingAndWith prepareMatchOf (NodeDescription <?> nodeDescription ,
103
- @ Nullable Condition condition ) {
107
+ @ Nullable Condition condition ) {
108
+
109
+ return prepareMatchOf (nodeDescription , condition , Collections .emptyList ());
110
+ }
111
+
112
+ public StatementBuilder .OrderableOngoingReadingAndWith prepareMatchOf (NodeDescription <?> nodeDescription ,
113
+ @ Nullable Condition condition , List <String > includedProperties ) {
104
114
105
115
String primaryLabel = nodeDescription .getPrimaryLabel ();
106
116
List <String > additionalLabels = nodeDescription .getAdditionalLabels ();
@@ -111,7 +121,99 @@ public StatementBuilder.OrderableOngoingReadingAndWith prepareMatchOf(NodeDescri
111
121
expressions .add (Constants .NAME_OF_ROOT_NODE );
112
122
expressions .add (Functions .id (rootNode ).as (Constants .NAME_OF_INTERNAL_ID ));
113
123
114
- return match (rootNode ).where (conditionOrNoCondition (condition )).with (expressions .toArray (new Expression [] {}));
124
+ if (nodeDescription .containsPossibleCircles (includedProperties )) {
125
+ return createPathMatchWithCondition (nodeDescription , includedProperties , condition , rootNode );
126
+ } else {
127
+ return match (rootNode ).where (conditionOrNoCondition (condition )).with (expressions .toArray (new Expression [] {}));
128
+ }
129
+ }
130
+
131
+ private StatementBuilder .OrderableOngoingReadingAndWithWithoutWhere createPathMatchWithCondition (
132
+ NodeDescription <?> nodeDescription , List <String > includedProperties , @ Nullable Condition condition , Node rootNode ) {
133
+
134
+ return createPathMatchWithCondition (null , nodeDescription , includedProperties , condition , rootNode );
135
+ }
136
+
137
+ public StatementBuilder .OrderableOngoingReadingAndWithWithoutWhere createPathMatchWithCondition (
138
+ @ Nullable StatementBuilder .OngoingReadingWithoutWhere previousMatches ,
139
+ NodeDescription <?> nodeDescription , List <String > includedProperties , @ Nullable Condition condition , Node rootNode ) {
140
+
141
+ List <Expression > expressions1 = new ArrayList <>();
142
+ List <Expression > expressions2 = new ArrayList <>();
143
+
144
+ String aliasedPathName = "pathPattern" ;
145
+ Predicate <String > includeField = s -> includedProperties .isEmpty () || includedProperties .contains (s );
146
+ Collection <RelationshipDescription > relationships = getRelationshipDescriptionsUpAndDown (nodeDescription , includeField );
147
+ RelationshipPattern patternPath = createRelationships (rootNode , relationships );
148
+ NamedPath path = Cypher .path ("p" ).definedBy (patternPath );
149
+
150
+ // nested nodes flatMap: reduce(...reduce(...))
151
+ SymbolicName outerNodesAccumulator = Cypher .name ("a" );
152
+ SymbolicName outerNodesVariable = Cypher .name ("b" );
153
+ SymbolicName innerNodesAccumulator = Cypher .name ("c" );
154
+ SymbolicName innerNodesVariable = Cypher .name ("d" );
155
+ SymbolicName innerNodesListIterator = Cypher .name ("e" );
156
+ ListComprehension innerNodesListComprehension = Cypher .listWith (innerNodesListIterator )
157
+ .in (Cypher .name (aliasedPathName )).returning (Functions .nodes (innerNodesListIterator ));
158
+
159
+ FunctionInvocation innerNodesReduce = createInnerReduce (innerNodesAccumulator , innerNodesVariable ,
160
+ innerNodesListComprehension );
161
+
162
+ FunctionInvocation outerNodesReduce = createOuterReduce (outerNodesAccumulator , outerNodesVariable ,
163
+ innerNodesReduce );
164
+
165
+ // nested relationships flatMap: reduce(...reduce(...))
166
+ SymbolicName outerRelationshipsAccumulator = Cypher .name ("f" );
167
+ SymbolicName outerRelationshipsVariable = Cypher .name ("g" );
168
+ SymbolicName innerRelationshipsAccumulator = Cypher .name ("h" );
169
+ SymbolicName innerRelationshipsVariable = Cypher .name ("i" );
170
+ SymbolicName innerRelationshipsListIterator = Cypher .name ("j" );
171
+ ListComprehension innerRelationshipsListComprehension = Cypher .listWith (innerRelationshipsListIterator )
172
+ .in (Cypher .name (aliasedPathName )).returning (Functions .relationships (innerRelationshipsListIterator ));
173
+
174
+ FunctionInvocation innerRelationshipReduce = createInnerReduce (innerRelationshipsAccumulator ,
175
+ innerRelationshipsVariable , innerRelationshipsListComprehension );
176
+
177
+ FunctionInvocation outerRelationshipsReduce = createOuterReduce (outerRelationshipsAccumulator ,
178
+ outerRelationshipsVariable , innerRelationshipReduce );
179
+
180
+ // WITH n, collect(p) as pathPattern
181
+ expressions1 .add (Constants .NAME_OF_ROOT_NODE );
182
+ expressions1 .add (Functions .collect (path ).as (aliasedPathName ));
183
+ // WITH n, reduce(nodes) as __sm__, reduce(relationships) as __sr__
184
+ expressions2 .add (Constants .NAME_OF_ROOT_NODE );
185
+ expressions2 .add (outerNodesReduce .as (Constants .NAME_OF_SYNTHESIZED_RELATED_NODES ));
186
+ expressions2 .add (outerRelationshipsReduce .as (Constants .NAME_OF_SYNTHESIZED_RELATIONS ));
187
+
188
+ StatementBuilder .OngoingReadingWithoutWhere match = match (path );
189
+
190
+ if (previousMatches != null ) {
191
+ match = previousMatches .match (path );
192
+ }
193
+
194
+ return match
195
+ .where (conditionOrNoCondition (condition ))
196
+ .with (expressions1 .toArray (new Expression []{}))
197
+ .with (expressions2 .toArray (new Expression []{}));
198
+ }
199
+
200
+ private FunctionInvocation createOuterReduce (SymbolicName outerNodesAccumulator , SymbolicName outerNodesVariable , FunctionInvocation innerNodesReduce ) {
201
+ return Functions .reduce (outerNodesVariable )
202
+ .in (innerNodesReduce )
203
+ .map (Cypher .caseExpression ()
204
+ .when (outerNodesVariable .in (outerNodesAccumulator ))
205
+ .then (outerNodesAccumulator )
206
+ .elseDefault (outerNodesAccumulator .add (outerNodesVariable )))
207
+ .accumulateOn (outerNodesAccumulator )
208
+ .withInitialValueOf (Cypher .listOf ());
209
+ }
210
+
211
+ private FunctionInvocation createInnerReduce (SymbolicName innerNodesAccumulator , SymbolicName innerNodesVariable , ListComprehension innerNodesListComprehension ) {
212
+ return Functions .reduce (innerNodesVariable )
213
+ .in (innerNodesListComprehension )
214
+ .map (innerNodesAccumulator .add (innerNodesVariable ))
215
+ .accumulateOn (innerNodesAccumulator )
216
+ .withInitialValueOf (Cypher .listOf ());
115
217
}
116
218
117
219
/**
@@ -332,8 +434,8 @@ public Statement prepareDeleteOf(
332
434
.build ();
333
435
}
334
436
335
- public Expression createReturnStatementForMatch (NodeDescription <?> nodeDescription ) {
336
- return createReturnStatementForMatch (nodeDescription , null );
437
+ public Expression [] createReturnStatementForMatch (NodeDescription <?> nodeDescription ) {
438
+ return createReturnStatementForMatch (nodeDescription , Collections . emptyList () );
337
439
}
338
440
339
441
/**
@@ -372,20 +474,25 @@ public Expression createReturnStatementForMatch(NodeDescription<?> nodeDescripti
372
474
373
475
/**
374
476
* @param nodeDescription Description of the root node
375
- * @param inputProperties A list of Java properties of the domain to be included. Those properties are compared with
477
+ * @param includedProperties A list of Java properties of the domain to be included. Those properties are compared with
376
478
* the field names of graph properties respectively relationships.
377
479
* @return An expresion to be returned by a Cypher statement
378
480
*/
379
- public Expression createReturnStatementForMatch (NodeDescription <?> nodeDescription ,
380
- @ Nullable List <String > inputProperties ) {
481
+ public Expression [] createReturnStatementForMatch (NodeDescription <?> nodeDescription ,
482
+ List <String > includedProperties ) {
381
483
382
- Predicate <String > includeField = s -> inputProperties == null || inputProperties .isEmpty ()
383
- || inputProperties .contains (s );
384
-
385
- SymbolicName nodeName = Constants .NAME_OF_ROOT_NODE ;
386
484
List <RelationshipDescription > processedRelationships = new ArrayList <>();
387
-
388
- return projectPropertiesAndRelationships (nodeDescription , nodeName , includeField , processedRelationships );
485
+ if (nodeDescription .containsPossibleCircles (includedProperties )) {
486
+ List <Expression > returnExpressions = new ArrayList <>();
487
+ Node rootNode = anyNode (Constants .NAME_OF_ROOT_NODE );
488
+ returnExpressions .add (rootNode .as (Constants .NAME_OF_SYNTHESIZED_ROOT_NODE ));
489
+ returnExpressions .add (Cypher .name (Constants .NAME_OF_SYNTHESIZED_RELATED_NODES ));
490
+ returnExpressions .add (Cypher .name (Constants .NAME_OF_SYNTHESIZED_RELATIONS ));
491
+ return returnExpressions .toArray (new Expression []{});
492
+ } else {
493
+ Predicate <String > includeField = s -> includedProperties .isEmpty () || includedProperties .contains (s );
494
+ return new Expression []{projectPropertiesAndRelationships (nodeDescription , Constants .NAME_OF_ROOT_NODE , includeField , processedRelationships )};
495
+ }
389
496
}
390
497
391
498
// recursive entry point for relationships in return statement
@@ -398,30 +505,23 @@ private MapProjection projectAllPropertiesAndRelationships(NodeDescription<?> no
398
505
}
399
506
400
507
private MapProjection projectPropertiesAndRelationships (NodeDescription <?> nodeDescription , SymbolicName nodeName ,
401
- Predicate <String > includeProperty , List <RelationshipDescription > processedRelationships ) {
508
+ Predicate <String > includedProperties , List <RelationshipDescription > processedRelationships ) {
402
509
403
- List <Object > propertiesProjection = projectNodeProperties (nodeDescription , nodeName , includeProperty );
510
+ List <Object > propertiesProjection = projectNodeProperties (nodeDescription , nodeName , includedProperties );
404
511
List <Object > contentOfProjection = new ArrayList <>(propertiesProjection );
405
512
406
- Collection <RelationshipDescription > relationships = getRelationshipDescriptionsUpAndDown (nodeDescription );
407
- relationships .removeIf (r -> !includeProperty .test (r .getFieldName ()));
513
+ Collection <RelationshipDescription > relationships = getRelationshipDescriptionsUpAndDown (nodeDescription , includedProperties );
514
+ relationships .removeIf (r -> !includedProperties .test (r .getFieldName ()));
408
515
409
- if (nodeDescription .containsPossibleCircles ()) {
410
- Node node = anyNode (nodeName );
411
- RelationshipPattern pattern = createRelationships (node , relationships );
412
- NamedPath p = Cypher .path ("p" ).definedBy (pattern );
413
- contentOfProjection .add (Constants .NAME_OF_PATHS );
414
- contentOfProjection .add (Cypher .listBasedOn (p ).returning (p ));
415
- } else {
416
- contentOfProjection .addAll (generateListsFor (relationships , nodeName , processedRelationships ));
417
- }
516
+ contentOfProjection .addAll (generateListsFor (relationships , nodeName , processedRelationships ));
418
517
return Cypher .anyNode (nodeName ).project (contentOfProjection );
419
518
}
420
519
421
520
@ NonNull
422
- static Collection <RelationshipDescription > getRelationshipDescriptionsUpAndDown (NodeDescription <?> nodeDescription ) {
423
- Collection < RelationshipDescription > relationships = new HashSet <>( nodeDescription . getRelationships ());
521
+ static Collection <RelationshipDescription > getRelationshipDescriptionsUpAndDown (NodeDescription <?> nodeDescription ,
522
+ Predicate < String > includedProperties ) {
424
523
524
+ Collection <RelationshipDescription > relationships = new HashSet <>(nodeDescription .getRelationships ());
425
525
for (NodeDescription <?> childDescription : nodeDescription .getChildNodeDescriptionsInHierarchy ()) {
426
526
childDescription .getRelationships ().forEach (concreteRelationship -> {
427
527
@@ -432,19 +532,25 @@ static Collection<RelationshipDescription> getRelationshipDescriptionsUpAndDown(
432
532
}
433
533
});
434
534
}
435
- return relationships ;
535
+
536
+ return relationships .stream ().filter (relationshipDescription ->
537
+ includedProperties .test (relationshipDescription .getFieldName ()))
538
+ .collect (Collectors .toSet ());
436
539
}
437
540
438
541
private RelationshipPattern createRelationships (Node node , Collection <RelationshipDescription > relationshipDescriptions ) {
439
542
RelationshipPattern relationship ;
440
543
441
544
Direction determinedDirection = determineDirection (relationshipDescriptions );
442
545
if (Direction .OUTGOING .equals (determinedDirection )) {
443
- relationship = node .relationshipTo (anyNode (), collectFirstLevelRelationshipTypes (relationshipDescriptions ));
546
+ relationship = node .relationshipTo (anyNode (), collectFirstLevelRelationshipTypes (relationshipDescriptions ))
547
+ .min (0 ).max (1 );
444
548
} else if (Direction .INCOMING .equals (determinedDirection )) {
445
- relationship = node .relationshipFrom (anyNode (), collectFirstLevelRelationshipTypes (relationshipDescriptions ));
549
+ relationship = node .relationshipFrom (anyNode (), collectFirstLevelRelationshipTypes (relationshipDescriptions ))
550
+ .min (0 ).max (1 );
446
551
} else {
447
- relationship = node .relationshipBetween (anyNode (), collectFirstLevelRelationshipTypes (relationshipDescriptions ));
552
+ relationship = node .relationshipBetween (anyNode (), collectFirstLevelRelationshipTypes (relationshipDescriptions ))
553
+ .min (0 ).max (1 );
448
554
}
449
555
450
556
Set <RelationshipDescription > processedRelationshipDescriptions = new HashSet <>(relationshipDescriptions );
0 commit comments