Skip to content

Commit 048b8af

Browse files
GH-2165 - Support nested projections with cascaded queries.
This closes #2165.
1 parent 8bcb946 commit 048b8af

File tree

11 files changed

+197
-56
lines changed

11 files changed

+197
-56
lines changed

src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,25 @@
1515
*/
1616
package org.springframework.data.neo4j.core;
1717

18+
import static org.neo4j.cypherdsl.core.Cypher.anyNode;
19+
import static org.neo4j.cypherdsl.core.Cypher.asterisk;
20+
import static org.neo4j.cypherdsl.core.Cypher.parameter;
21+
22+
import java.util.ArrayList;
23+
import java.util.Collection;
24+
import java.util.Collections;
25+
import java.util.HashMap;
26+
import java.util.HashSet;
27+
import java.util.LinkedHashSet;
28+
import java.util.List;
29+
import java.util.Map;
30+
import java.util.Optional;
31+
import java.util.Set;
32+
import java.util.function.Consumer;
33+
import java.util.function.Function;
34+
import java.util.function.Predicate;
35+
import java.util.stream.Collectors;
36+
1837
import org.apache.commons.logging.LogFactory;
1938
import org.apiguardian.api.API;
2039
import org.neo4j.cypherdsl.core.Condition;
@@ -56,24 +75,6 @@
5675
import org.springframework.util.Assert;
5776
import org.springframework.util.CollectionUtils;
5877

59-
import java.util.ArrayList;
60-
import java.util.Collection;
61-
import java.util.Collections;
62-
import java.util.HashMap;
63-
import java.util.HashSet;
64-
import java.util.LinkedHashSet;
65-
import java.util.List;
66-
import java.util.Map;
67-
import java.util.Optional;
68-
import java.util.Set;
69-
import java.util.function.Consumer;
70-
import java.util.function.Function;
71-
import java.util.stream.Collectors;
72-
73-
import static org.neo4j.cypherdsl.core.Cypher.anyNode;
74-
import static org.neo4j.cypherdsl.core.Cypher.asterisk;
75-
import static org.neo4j.cypherdsl.core.Cypher.parameter;
76-
7778
/**
7879
* @author Michael J. Simons
7980
* @author Philipp Tölle
@@ -651,11 +652,7 @@ private Optional<Neo4jClient.RecordFetchSpec<T>> createFetchSpec() {
651652
QueryFragmentsAndParameters.QueryFragments queryFragments = queryFragmentsAndParameters.getQueryFragments();
652653
Neo4jPersistentEntity<?> entityMetaData = (Neo4jPersistentEntity<?>) queryFragmentsAndParameters.getNodeDescription();
653654

654-
QueryFragmentsAndParameters.QueryFragments.ReturnTuple returnTuple = queryFragments.getReturnTuple();
655-
boolean containsPossibleCircles = entityMetaData != null && entityMetaData.containsPossibleCircles(
656-
returnTuple != null
657-
? returnTuple.getIncludedProperties()
658-
: Collections.emptyList());
655+
boolean containsPossibleCircles = entityMetaData != null && entityMetaData.containsPossibleCircles(queryFragments::includeField);
659656
if (cypherQuery == null || containsPossibleCircles) {
660657

661658
Map<String, Object> parameters = queryFragmentsAndParameters.getParameters();
@@ -705,10 +702,11 @@ private GenericQueryAndParameters createQueryAndParameters(Neo4jPersistentEntity
705702
final Set<Long> relationshipIds = new HashSet<>();
706703
final Set<Long> relatedNodeIds = new HashSet<>();
707704

705+
Predicate<RelationshipDescription> relationshipFilter = ((Predicate<RelationshipDescription>) relationshipDescription ->
706+
queryFragments.includeField(relationshipDescription.getFieldName())).negate();
707+
708708
for (RelationshipDescription relationshipDescription : entityMetaData.getRelationships()) {
709-
if (queryFragments.getReturnTuple() != null
710-
&& !queryFragments.getReturnTuple().getIncludedProperties().isEmpty()
711-
&& queryFragments.getReturnTuple().getIncludedProperties().contains(relationshipDescription.getFieldName())) {
709+
if (relationshipFilter.test(relationshipDescription)) {
712710
continue;
713711
}
714712

src/main/java/org/springframework/data/neo4j/core/PreparedQuery.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
*
5353
* @param <T> The type of the objects returned by this query.
5454
* @author Michael J. Simons
55+
* @author Gerrit Meier
5556
* @soundtrack Deichkind - Arbeit nervt
5657
* @since 6.0
5758
*/

src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -449,11 +449,7 @@ private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType,
449449
QueryFragmentsAndParameters.QueryFragments queryFragments = queryFragmentsAndParameters.getQueryFragments();
450450
Map<String, Object> parameters = queryFragmentsAndParameters.getParameters();
451451

452-
QueryFragmentsAndParameters.QueryFragments.ReturnTuple returnTuple = queryFragments.getReturnTuple();
453-
boolean containsPossibleCircles = entityMetaData != null && entityMetaData.containsPossibleCircles(
454-
returnTuple != null
455-
? returnTuple.getIncludedProperties()
456-
: Collections.emptyList());
452+
boolean containsPossibleCircles = entityMetaData != null && entityMetaData.containsPossibleCircles(queryFragments::includeField);
457453
if (containsPossibleCircles && !queryFragments.isScalarValueReturn()) {
458454
return createQueryAndParameters(entityMetaData, queryFragments, parameters)
459455
.flatMap(finalQueryAndParameters ->
@@ -470,9 +466,7 @@ private Mono<GenericQueryAndParameters> createQueryAndParameters(Neo4jPersistent
470466
QueryFragmentsAndParameters.QueryFragments queryFragments, Map<String, Object> parameters) {
471467

472468
Predicate<RelationshipDescription> relationshipFilter = relationshipDescription ->
473-
queryFragments.getReturnTuple() == null
474-
|| queryFragments.getReturnTuple().getIncludedProperties().isEmpty()
475-
|| queryFragments.getReturnTuple().getIncludedProperties().contains(relationshipDescription.getFieldName());
469+
queryFragments.includeField(relationshipDescription.getFieldName());
476470

477471
return getDatabaseName().flatMap(databaseName -> {
478472
return Mono.deferContextual(ctx -> {
@@ -750,11 +744,7 @@ public <T> Mono<ExecutableQuery<T>> toExecutableQuery(PreparedQuery<T> preparedQ
750744
QueryFragmentsAndParameters.QueryFragments queryFragments = queryFragmentsAndParameters.getQueryFragments();
751745
Neo4jPersistentEntity<?> entityMetaData = (Neo4jPersistentEntity<?>) queryFragmentsAndParameters.getNodeDescription();
752746

753-
QueryFragmentsAndParameters.QueryFragments.ReturnTuple returnTuple = queryFragments.getReturnTuple();
754-
boolean containsPossibleCircles = entityMetaData != null && entityMetaData.containsPossibleCircles(
755-
returnTuple != null
756-
? returnTuple.getIncludedProperties()
757-
: Collections.emptyList());
747+
boolean containsPossibleCircles = entityMetaData != null && entityMetaData.containsPossibleCircles(queryFragments::includeField);
758748
if (cypherQuery == null || containsPossibleCircles) {
759749

760750
Map<String, Object> parameters = queryFragmentsAndParameters.getParameters();

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import java.util.ArrayList;
4545
import java.util.Arrays;
4646
import java.util.Collection;
47-
import java.util.Collections;
4847
import java.util.HashSet;
4948
import java.util.List;
5049
import java.util.Set;
@@ -430,7 +429,7 @@ public Statement prepareDeleteOf(
430429
}
431430

432431
public Expression[] createReturnStatementForMatch(NodeDescription<?> nodeDescription) {
433-
return createReturnStatementForMatch(nodeDescription, Collections.emptyList());
432+
return createReturnStatementForMatch(nodeDescription, fieldName -> true);
434433
}
435434

436435
/**
@@ -469,18 +468,17 @@ public Expression[] createReturnStatementForMatch(NodeDescription<?> nodeDescrip
469468

470469
/**
471470
* @param nodeDescription Description of the root node
472-
* @param includedProperties A list of Java properties of the domain to be included. Those properties are compared with
473-
* the field names of graph properties respectively relationships.
471+
* @param includeField A predicate derived from the set of included properties. This is only relevant in various forms
472+
* of projections which allow to exclude one or more fields.
474473
* @return An expresion to be returned by a Cypher statement
475474
*/
476475
public Expression[] createReturnStatementForMatch(NodeDescription<?> nodeDescription,
477-
List<String> includedProperties) {
476+
Predicate<String> includeField) {
478477

479478
List<RelationshipDescription> processedRelationships = new ArrayList<>();
480-
if (nodeDescription.containsPossibleCircles(includedProperties)) {
479+
if (nodeDescription.containsPossibleCircles(includeField)) {
481480
return createGenericReturnStatement();
482481
} else {
483-
Predicate<String> includeField = s -> includedProperties.isEmpty() || includedProperties.contains(s);
484482
return new Expression[]{projectPropertiesAndRelationships(nodeDescription, Constants.NAME_OF_ROOT_NODE, includeField, processedRelationships)};
485483
}
486484
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Set;
2727
import java.util.TreeSet;
2828
import java.util.UUID;
29+
import java.util.function.Predicate;
2930
import java.util.function.Supplier;
3031
import java.util.stream.Collectors;
3132
import java.util.stream.Stream;
@@ -470,16 +471,16 @@ public NodeDescription<?> getParentNodeDescription() {
470471
}
471472

472473
@Override
473-
public boolean containsPossibleCircles(List<String> includedProperties) {
474-
return calculatePossibleCircles(includedProperties);
474+
public boolean containsPossibleCircles(Predicate<String> includeField) {
475+
return calculatePossibleCircles(includeField);
475476
}
476477

477-
private boolean calculatePossibleCircles(List<String> includedProperties) {
478+
private boolean calculatePossibleCircles(Predicate<String> includeField) {
478479
Collection<RelationshipDescription> relationships = getRelationships();
479480

480481
Set<RelationshipDescription> processedRelationships = new HashSet<>();
481482
for (RelationshipDescription relationship : relationships) {
482-
if (!includedProperties.isEmpty() && !includedProperties.contains(relationship.getFieldName())) {
483+
if (!includeField.test(relationship.getFieldName())) {
483484
continue;
484485
}
485486
if (processedRelationships.contains(relationship)) {

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Collection;
2020
import java.util.List;
2121
import java.util.Optional;
22+
import java.util.function.Predicate;
2223

2324
import org.apiguardian.api.API;
2425
import org.neo4j.cypherdsl.core.Expression;
@@ -135,7 +136,18 @@ default Expression getIdExpression() {
135136

136137
/**
137138
* @return Information if the domain would contain schema circles.
139+
* @deprecated since 6.0.6, this is going away in 6.1 and should not be used as external API.
138140
*/
139-
boolean containsPossibleCircles(List<String> includeProperties);
141+
@Deprecated
142+
default boolean containsPossibleCircles(List<String> includeProperties) {
143+
return containsPossibleCircles(s -> includeProperties.contains(s));
144+
}
145+
146+
/**
147+
*
148+
* @param includeField A predicate used to determine the properties that need to be looked at while detecting possible circles.
149+
* @return True if the domain would contain schema circles.
150+
*/
151+
boolean containsPossibleCircles(Predicate<String> includeField);
140152

141153
}

src/main/java/org/springframework/data/neo4j/repository/query/QueryFragmentsAndParameters.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@
3636

3737
import java.util.ArrayList;
3838
import java.util.Arrays;
39+
import java.util.Collection;
3940
import java.util.Collections;
41+
import java.util.HashSet;
4042
import java.util.List;
4143
import java.util.Map;
44+
import java.util.Set;
4245

4346
import static org.neo4j.cypherdsl.core.Cypher.parameter;
4447

@@ -218,6 +221,10 @@ public void setReturnExpression(Expression returnExpression, boolean isScalarVal
218221
this.scalarValueReturn = isScalarValue;
219222
}
220223

224+
public boolean includeField(String fieldName) {
225+
return this.returnTuple == null || this.returnTuple.includedProperties.isEmpty() || this.returnTuple.includedProperties.contains(fieldName);
226+
}
227+
221228
public void setOrderBy(SortItem[] orderBy) {
222229
this.orderBy = orderBy;
223230
}
@@ -246,7 +253,7 @@ private Expression[] getReturnExpressions() {
246253
return returnExpressions.size() > 0
247254
? returnExpressions.toArray(new Expression[]{})
248255
: CypherGenerator.INSTANCE.createReturnStatementForMatch(getReturnTuple().getNodeDescription(),
249-
getReturnTuple().getIncludedProperties());
256+
this::includeField);
250257
}
251258

252259
private SortItem[] getOrderBy() {
@@ -279,18 +286,18 @@ public Statement toStatement() {
279286
@API(status = API.Status.INTERNAL, since = "6.0.4")
280287
public final static class ReturnTuple {
281288
private final NodeDescription<?> nodeDescription;
282-
private final List<String> includedProperties;
289+
private final Set<String> includedProperties;
283290

284291
private ReturnTuple(NodeDescription<?> nodeDescription, List<String> includedProperties) {
285292
this.nodeDescription = nodeDescription;
286-
this.includedProperties = includedProperties;
293+
this.includedProperties = includedProperties == null ? Collections.emptySet() : new HashSet<>(includedProperties);
287294
}
288295

289296
public NodeDescription<?> getNodeDescription() {
290297
return nodeDescription;
291298
}
292299

293-
public List<String> getIncludedProperties() {
300+
public Collection<String> getIncludedProperties() {
294301
return includedProperties;
295302
}
296303
}

0 commit comments

Comments
 (0)