GH-2858 - Fix following paths in projections.
If an entity has already been loaded by any relationship, it gets marked as processed. But this is not a valid state if there are multiple relationships to this entity and it is loaded via different projections for each relationship. In those cases SDN will just stop to find other relationships. This commit fixes this behaviour by also taking the relationship the entity got loaded with into account.
This commit is contained in:
@@ -1202,8 +1202,8 @@ public final class Neo4jTemplate implements
|
||||
return NodesAndRelationshipsByIdStatementProvider.EMPTY;
|
||||
}
|
||||
// load first level relationships
|
||||
final Set<String> relationshipIds = new HashSet<>();
|
||||
final Set<String> relatedNodeIds = new HashSet<>();
|
||||
// final Set<String> relationshipIds = new HashSet<>();
|
||||
final Map<String, Set<String>> relationshipsToRelatedNodeIds = new HashMap<>();
|
||||
|
||||
for (RelationshipDescription relationshipDescription : entityMetaData.getRelationshipsInHierarchy(queryFragments::includeField)) {
|
||||
|
||||
@@ -1217,14 +1217,14 @@ public final class Neo4jTemplate implements
|
||||
.bindAll(usedParameters)
|
||||
.fetch()
|
||||
.one()
|
||||
.ifPresent(iterateAndMapNextLevel(relationshipIds, relatedNodeIds, relationshipDescription, PropertyPathWalkStep.empty()));
|
||||
.ifPresent(iterateAndMapNextLevel(relationshipsToRelatedNodeIds, relationshipDescription, PropertyPathWalkStep.empty()));
|
||||
}
|
||||
|
||||
return new NodesAndRelationshipsByIdStatementProvider(rootNodeIds, relationshipIds, relatedNodeIds, queryFragments, elementIdOrIdFunction);
|
||||
return new NodesAndRelationshipsByIdStatementProvider(rootNodeIds, relationshipsToRelatedNodeIds.keySet(), relationshipsToRelatedNodeIds.values().stream().flatMap(Collection::stream).toList(), queryFragments, elementIdOrIdFunction);
|
||||
}
|
||||
|
||||
private void iterateNextLevel(Collection<String> nodeIds, RelationshipDescription sourceRelationshipDescription, Set<String> relationshipIds,
|
||||
Set<String> relatedNodeIds, PropertyPathWalkStep currentPathStep) {
|
||||
private void iterateNextLevel(Collection<String> nodeIds, RelationshipDescription sourceRelationshipDescription,
|
||||
Map<String, Set<String>> relationshipsToRelatedNodes, PropertyPathWalkStep currentPathStep) {
|
||||
|
||||
Neo4jPersistentEntity<?> target = (Neo4jPersistentEntity<?>) sourceRelationshipDescription.getTarget();
|
||||
|
||||
@@ -1258,32 +1258,42 @@ public final class Neo4jTemplate implements
|
||||
.bindAll(Collections.singletonMap(Constants.NAME_OF_IDS, TemplateSupport.convertToLongIdOrStringElementId(nodeIds)))
|
||||
.fetch()
|
||||
.one()
|
||||
.ifPresent(iterateAndMapNextLevel(relationshipIds, relatedNodeIds, relationshipDescription, nextPathStep));
|
||||
.ifPresent(iterateAndMapNextLevel(relationshipsToRelatedNodes, relationshipDescription, nextPathStep));
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Consumer<Map<String, Object>> iterateAndMapNextLevel(Set<String> relationshipIds,
|
||||
Set<String> relatedNodeIds,
|
||||
private Consumer<Map<String, Object>> iterateAndMapNextLevel(Map<String, Set<String>> relationshipsToRelatedNodes,
|
||||
RelationshipDescription relationshipDescription,
|
||||
PropertyPathWalkStep currentPathStep) {
|
||||
|
||||
return record -> {
|
||||
|
||||
Map<String, Set<String>> relatedNodesVisited = new HashMap<>(relationshipsToRelatedNodes);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> newRelationshipIds = ((List<Object>) record.get(Constants.NAME_OF_SYNTHESIZED_RELATIONS)).stream().map(TemplateSupport::convertIdOrElementIdToString).toList();
|
||||
relationshipIds.addAll(newRelationshipIds);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> newRelatedNodeIds = ((List<Object>) record.get(Constants.NAME_OF_SYNTHESIZED_RELATED_NODES)).stream().map(TemplateSupport::convertIdOrElementIdToString).toList();
|
||||
Set<String> relatedIds = new HashSet<>(((List<Object>) record.get(Constants.NAME_OF_SYNTHESIZED_RELATED_NODES)).stream().map(TemplateSupport::convertIdOrElementIdToString).toList());
|
||||
|
||||
Set<String> relatedIds = new HashSet<>(newRelatedNodeIds);
|
||||
// use this list to get down the road
|
||||
// 1. remove already visited ones;
|
||||
relatedIds.removeAll(relatedNodeIds);
|
||||
relatedNodeIds.addAll(relatedIds);
|
||||
// we don't know which id came with which node, so we need to assume that a relationshipId connects to all related nodes
|
||||
for (String newRelationshipId : newRelationshipIds) {
|
||||
relatedNodesVisited.put(newRelationshipId, relatedIds);
|
||||
Set<String> knownRelatedNodesBefore = relationshipsToRelatedNodes.get(newRelationshipId);
|
||||
if (knownRelatedNodesBefore != null) {
|
||||
Set<String> mergedKnownRelatedNodes = new HashSet<>(knownRelatedNodesBefore);
|
||||
// there are already existing nodes in there for this relationship
|
||||
mergedKnownRelatedNodes.addAll(relatedIds);
|
||||
relatedNodesVisited.put(newRelationshipId, mergedKnownRelatedNodes);
|
||||
relatedIds.removeAll(knownRelatedNodesBefore);
|
||||
}
|
||||
}
|
||||
|
||||
relationshipsToRelatedNodes.putAll(relatedNodesVisited);
|
||||
// 2. for the rest start the exploration
|
||||
if (!relatedIds.isEmpty()) {
|
||||
iterateNextLevel(relatedIds, relationshipDescription, relationshipIds, relatedNodeIds, currentPathStep);
|
||||
iterateNextLevel(relatedIds, relationshipDescription, relationshipsToRelatedNodes, currentPathStep);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -720,8 +720,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
Class<?> rootClass = entityMetaData.getUnderlyingClass();
|
||||
|
||||
Set<String> rootNodeIds = ctx.get("rootNodes");
|
||||
Set<String> processedRelationshipIds = ctx.get("processedRelationships");
|
||||
Set<String> processedNodeIds = ctx.get("processedNodes");
|
||||
Map<String, Set<String>> relationshipsToRelatedNodeIds = ctx.get("relationshipsToRelatedNodeIds");
|
||||
return Flux.fromIterable(entityMetaData.getRelationshipsInHierarchy(queryFragments::includeField))
|
||||
.concatMap(relationshipDescription -> {
|
||||
|
||||
@@ -748,12 +747,11 @@ public final class ReactiveNeo4jTemplate implements
|
||||
})
|
||||
.expand(iterateAndMapNextLevel(relationshipDescription, queryFragments, rootClass, PropertyPathWalkStep.empty()));
|
||||
})
|
||||
.then(Mono.fromSupplier(() -> new NodesAndRelationshipsByIdStatementProvider(rootNodeIds, processedRelationshipIds, processedNodeIds, queryFragments, elementIdOrIdFunction)));
|
||||
.then(Mono.fromSupplier(() -> new NodesAndRelationshipsByIdStatementProvider(rootNodeIds, relationshipsToRelatedNodeIds.keySet(), relationshipsToRelatedNodeIds.values().stream().flatMap(Collection::stream).toList(), queryFragments, elementIdOrIdFunction)));
|
||||
})
|
||||
.contextWrite(ctx -> ctx
|
||||
.put("rootNodes", ConcurrentHashMap.newKeySet())
|
||||
.put("processedNodes", ConcurrentHashMap.newKeySet())
|
||||
.put("processedRelationships", ConcurrentHashMap.newKeySet()));
|
||||
.put("relationshipsToRelatedNodeIds", new ConcurrentHashMap<>()));
|
||||
|
||||
}
|
||||
|
||||
@@ -812,22 +810,29 @@ public final class ReactiveNeo4jTemplate implements
|
||||
|
||||
return newRelationshipAndRelatedNodeIds ->
|
||||
Flux.deferContextual(ctx -> {
|
||||
Set<String> relationshipIds = ctx.get("processedRelationships");
|
||||
Set<String> processedNodeIds = ctx.get("processedNodes");
|
||||
Map<String, Set<String>> relationshipsToRelatedNodeIds = ctx.get("relationshipsToRelatedNodeIds");
|
||||
Map<String, Set<String>> relatedNodesVisited = new HashMap<>(relationshipsToRelatedNodeIds);
|
||||
|
||||
Collection<String> newRelationshipIds = newRelationshipAndRelatedNodeIds.getT1();
|
||||
Set<String> tmpProcessedRels = ConcurrentHashMap.newKeySet(newRelationshipIds.size());
|
||||
tmpProcessedRels.addAll(newRelationshipIds);
|
||||
tmpProcessedRels.removeAll(relationshipIds);
|
||||
relationshipIds.addAll(newRelationshipIds);
|
||||
|
||||
Collection<String> newRelatedNodeIds = newRelationshipAndRelatedNodeIds.getT2();
|
||||
Set<String> tmpProcessedNodes = ConcurrentHashMap.newKeySet(newRelatedNodeIds.size());
|
||||
tmpProcessedNodes.addAll(newRelatedNodeIds);
|
||||
tmpProcessedNodes.removeAll(processedNodeIds);
|
||||
processedNodeIds.addAll(newRelatedNodeIds);
|
||||
Set<String> relatedIds = ConcurrentHashMap.newKeySet(newRelatedNodeIds.size());
|
||||
relatedIds.addAll(newRelatedNodeIds);
|
||||
|
||||
if (tmpProcessedRels.isEmpty() && tmpProcessedNodes.isEmpty()) {
|
||||
for (String newRelationshipId : newRelationshipIds) {
|
||||
relatedNodesVisited.put(newRelationshipId, relatedIds);
|
||||
Set<String> knownRelatedNodesBefore = relationshipsToRelatedNodeIds.get(newRelationshipId);
|
||||
if (knownRelatedNodesBefore != null) {
|
||||
Set<String> mergedKnownRelatedNodes = new HashSet<>(knownRelatedNodesBefore);
|
||||
// there are already existing nodes in there for this relationship
|
||||
mergedKnownRelatedNodes.addAll(relatedIds);
|
||||
relatedNodesVisited.put(newRelationshipId, mergedKnownRelatedNodes);
|
||||
relatedIds.removeAll(knownRelatedNodesBefore);
|
||||
}
|
||||
}
|
||||
relationshipsToRelatedNodeIds.putAll(relatedNodesVisited);
|
||||
|
||||
if (relatedIds.isEmpty()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user