Skip to content

Use a sql recursive query for node tree invalidation and dto tree building #791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,32 @@ public interface NodeRepository extends JpaRepository<NodeEntity, UUID> {
List<NodeEntity> findAllByStudyIdAndTypeAndStashed(UUID id, NodeType type, boolean stashed);

@Query(nativeQuery = true, value =
"WITH RECURSIVE NodeHierarchy (id_node, depth) AS ( " +
" SELECT n0.id_node, 0 AS depth" +
"WITH RECURSIVE NodeHierarchy (id_node) AS ( " +
" SELECT n0.id_node" +
" FROM NODE n0 " +
" WHERE id_node = :nodeUuid " +
" UNION ALL " +
" SELECT n.id_node, nh.depth + 1 as depth" +
" SELECT n.id_node" +
" FROM NODE n " +
" INNER JOIN NodeHierarchy nh ON n.parent_node = nh.id_node " +
") " +
"SELECT nh.id_node::text " +
"FROM NodeHierarchy nh " +
"ORDER BY nh.depth DESC")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure that ordering by depth was not necessary anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

List<String> findAllDescendants(UUID nodeUuid);
"SELECT cast(nh.id_node AS VARCHAR) " +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is "cast()" needed here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes otherwise the result is of type string and not uuid

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it work without casting neither in postgres nor H2? or not working only in H2?

"FROM NodeHierarchy nh where nh.id_node != :nodeUuid ")
List<UUID> findAllChildrenUuids(UUID nodeUuid);

List<NodeEntity> findAllByIdNodeIn(List<UUID> uuids);
@Query(nativeQuery = true, value =
"WITH RECURSIVE NodeHierarchy (id_node) AS ( " +
" SELECT n0.id_node" +
" FROM NODE n0 " +
" WHERE id_node = :nodeUuid " +
" UNION ALL " +
" SELECT n.id_node" +
" FROM NODE n " +
" INNER JOIN NodeHierarchy nh ON n.parent_node = nh.id_node " +
") " +
"SELECT * FROM NODE n " +
"WHERE n.id_node IN (SELECT nh.id_node FROM NodeHierarchy nh) AND n.id_node != :nodeUuid")
List<NodeEntity> findAllChildren(UUID nodeUuid);

List<NodeEntity> findAllByStudyIdAndStashedAndParentNodeIdNodeOrderByStashDateDesc(UUID id, boolean stashed, UUID parentNode);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,6 @@ public interface RootNetworkNodeInfoRepository extends JpaRepository<RootNetwork
@Query("select count(rnni) > 0 from RootNetworkNodeInfoEntity rnni LEFT JOIN rnni.rootNetwork rn LEFT JOIN rn.study s " +
"where s.id = :studyUuid and (rnni.nodeBuildStatus.globalBuildStatus = :buildStatus or rnni.nodeBuildStatus.localBuildStatus = :buildStatus)")
boolean existsByStudyUuidAndBuildStatus(UUID studyUuid, BuildStatus buildStatus);

List<RootNetworkNodeInfoEntity> getAllByRootNetworkIdAndNodeInfoIdIn(UUID rootNetworkUuid, List<UUID> nodesUuids);
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ private NetworkModificationNode createAndInsertNode(StudyEntity study, UUID node
if (insertMode.equals(InsertMode.BEFORE)) {
reference.setParentNode(node);
} else if (insertMode.equals(InsertMode.AFTER)) {
nodesRepository.findAllByParentNodeIdNode(nodeId).stream()
getChildren(nodeId).stream()
.filter(n -> !n.getIdNode().equals(node.getIdNode()))
.forEach(child -> child.setParentNode(node));
}
Expand Down Expand Up @@ -238,7 +238,7 @@ public void moveStudyNode(UUID nodeToMoveUuid, UUID anchorNodeUuid, InsertMode i
private UUID moveNode(UUID nodeToMoveUuid, UUID anchorNodeUuid, InsertMode insertMode) {
NodeEntity nodeToMoveEntity = getNodeEntity(nodeToMoveUuid);

nodesRepository.findAllByParentNodeIdNode(nodeToMoveUuid)
getChildren(nodeToMoveUuid)
.forEach(child -> child.setParentNode(nodeToMoveEntity.getParentNode()));

NodeEntity anchorNodeEntity = getNodeEntity(anchorNodeUuid);
Expand All @@ -253,7 +253,7 @@ private UUID moveNode(UUID nodeToMoveUuid, UUID anchorNodeUuid, InsertMode inser
if (insertMode.equals(InsertMode.BEFORE)) {
anchorNodeEntity.setParentNode(nodeToMoveEntity);
} else if (insertMode.equals(InsertMode.AFTER)) {
nodesRepository.findAllByParentNodeIdNode(anchorNodeUuid).stream()
getChildren(anchorNodeUuid).stream()
.filter(n -> !n.getIdNode().equals(nodeToMoveEntity.getIdNode()))
.forEach(child -> child.setParentNode(nodeToMoveEntity));
}
Expand All @@ -264,7 +264,7 @@ private UUID moveNode(UUID nodeToMoveUuid, UUID anchorNodeUuid, InsertMode inser

@Transactional
public void moveStudySubtree(UUID parentNodeToMoveUuid, UUID anchorNodeUuid) {
List<NodeEntity> children = getChildrenByParentUuid(parentNodeToMoveUuid);
List<NodeEntity> children = getChildren(parentNodeToMoveUuid);
moveNode(parentNodeToMoveUuid, anchorNodeUuid, InsertMode.CHILD);
children.forEach(child -> self.moveStudySubtree(child.getIdNode(), parentNodeToMoveUuid));
}
Expand Down Expand Up @@ -299,9 +299,9 @@ private void stashNodes(UUID id, boolean stashChildren, List<UUID> stashedNodes,
UUID modificationGroupUuid = self.getModificationGroupUuid(nodeToStash.getIdNode());
networkModificationService.deleteStashedModifications(modificationGroupUuid);
if (!stashChildren) {
nodesRepository.findAllByParentNodeIdNode(id).forEach(node -> node.setParentNode(nodeToStash.getParentNode()));
getChildren(id).forEach(node -> node.setParentNode(nodeToStash.getParentNode()));
} else {
nodesRepository.findAllByParentNodeIdNode(id)
getChildren(id)
.forEach(child -> stashNodes(child.getIdNode(), true, stashedNodes, false));
}
stashedNodes.add(id);
Expand Down Expand Up @@ -329,9 +329,9 @@ private void deleteNodes(UUID id, boolean deleteChildren, boolean allowDeleteRoo
rootNetworkNodeInfoService.fillDeleteNodeInfo(id, deleteNodeInfos);

if (!deleteChildren) {
nodesRepository.findAllByParentNodeIdNode(id).forEach(node -> node.setParentNode(nodeToDelete.getParentNode()));
getChildren(id).forEach(node -> node.setParentNode(nodeToDelete.getParentNode()));
} else {
nodesRepository.findAllByParentNodeIdNode(id)
getChildren(id)
.forEach(child -> deleteNodes(child.getIdNode(), true, false, removedNodes, deleteNodeInfos));
}
removedNodes.add(id);
Expand All @@ -344,12 +344,28 @@ private void deleteNodes(UUID id, boolean deleteChildren, boolean allowDeleteRoo
});
}

public List<NodeEntity> getChildrenByParentUuid(UUID parentUuid) {
public List<NodeEntity> getChildren(UUID parentUuid) {
return nodesRepository.findAllByParentNodeIdNode(parentUuid);
}

public List<String> getAllChildrenFromParentUuid(UUID parentUuid) {
return nodesRepository.findAllDescendants(parentUuid);
public List<UUID> getAllChildrenUuids(UUID parentUuid) {
return nodesRepository.findAllChildrenUuids(parentUuid);
}

// TODO Remove this method and use getAllChildrenUuids
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we've already changed some "getChildren" to "getChildrenUuids" here, why don't we make this change right now by renaming all occurences ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is used in many places and you have to test a lot of things
A TS was created

public List<UUID> getChildrenUuids(UUID parentUuid) {
List<UUID> children = new ArrayList<>();
doGetChildrenUuids(parentUuid, children);
return children;
}

private void doGetChildrenUuids(UUID parentUuid, List<UUID> children) {
Optional<NodeEntity> optNode = nodesRepository.findById(parentUuid);
optNode.ifPresent(node -> getChildren(parentUuid)
.forEach(child -> {
children.add(child.getIdNode());
doGetChildrenUuids(child.getIdNode(), children);
}));
}

@Transactional
Expand Down Expand Up @@ -411,10 +427,7 @@ private void completeNodeInfos(List<AbstractNode> nodes, UUID rootNetworkUuid) {

@Transactional
public AbstractNode getStudySubtree(UUID studyId, UUID parentNodeUuid, UUID rootNetworkUuid) {
// TODO: not working because of proxy appearing in tests TOFIX later
// List<UUID> nodeUuids = nodesRepository.findAllDescendants(parentNodeUuid).stream().map(UUID::fromString).toList();
// List<NodeEntity> nodes = nodesRepository.findAllById(nodeUuids);
List<NodeEntity> nodes = nodesRepository.findAllByStudyId(studyId);
List<NodeEntity> nodes = nodesRepository.findAllChildren(parentNodeUuid);

List<AbstractNode> allNodeInfos = new ArrayList<>();
allNodeInfos.addAll(rootNodeInfoRepository.findAllByNodeStudyId(studyId).stream().map(RootNodeInfoEntity::toDto).toList());
Expand Down Expand Up @@ -561,7 +574,7 @@ private AbstractNode getSimpleNode(UUID nodeId) {
@Transactional
public AbstractNode getNode(UUID nodeId, UUID rootNetworkUuid) {
AbstractNode node = getSimpleNode(nodeId);
nodesRepository.findAllByParentNodeIdNode(node.getId()).stream().map(NodeEntity::getIdNode).forEach(node.getChildrenIds()::add);
getChildren(node.getId()).stream().map(NodeEntity::getIdNode).forEach(node.getChildrenIds()::add);
if (rootNetworkUuid != null) {
completeNodeInfos(List.of(node), rootNetworkUuid);
}
Expand Down Expand Up @@ -691,7 +704,7 @@ public List<Pair<AbstractNode, Integer>> getStashedNodes(UUID studyUuid) {
nodes.stream().map(node -> networkModificationNodeInfos.get(node.getIdNode()))
.forEach(abstractNode -> {
ArrayList<UUID> children = new ArrayList<>();
doGetChildren(abstractNode.getId(), children);
doGetChildrenUuids(abstractNode.getId(), children);
result.add(Pair.of(abstractNode, children.size()));
});
return result;
Expand Down Expand Up @@ -746,7 +759,7 @@ public Map<UUID, UUID> getModificationReports(UUID nodeUuid, UUID rootNetworkUui
}

private void restoreNodeChildren(UUID studyId, UUID parentNodeId) {
nodesRepository.findAllByParentNodeIdNode(parentNodeId).forEach(nodeEntity -> {
getChildren(parentNodeId).forEach(nodeEntity -> {
NetworkModificationNodeInfoEntity modificationNodeToRestore = networkModificationNodeInfoRepository.findById(nodeEntity.getIdNode()).orElseThrow(() -> new StudyException(NODE_NOT_FOUND));
if (self.isNodeNameExists(studyId, modificationNodeToRestore.getName())) {
String newName = getSuffixedNodeName(studyId, modificationNodeToRestore.getName());
Expand Down Expand Up @@ -883,7 +896,7 @@ private boolean hasAnyBuiltChildren(NodeEntity node, UUID rootNetworkUuid, Set<N
}
checkedChildren.add(node);

for (NodeEntity child : getChildrenByParentUuid(node.getIdNode())) {
for (NodeEntity child : getChildren(node.getIdNode())) {
if (!checkedChildren.contains(child)
&& hasAnyBuiltChildren(child, rootNetworkUuid, checkedChildren)) {
return true;
Expand Down Expand Up @@ -919,11 +932,12 @@ private void fillIndexedNodeTreeInfosToInvalidate(NodeEntity nodeEntity, UUID ro

private InvalidateNodeInfos invalidateChildrenNodes(UUID nodeUuid, UUID rootNetworkUuid) {
InvalidateNodeInfos invalidateNodeInfos = new InvalidateNodeInfos();
nodesRepository.findAllByParentNodeIdNode(nodeUuid)
.forEach(child -> {
invalidateNodeInfos.add(rootNetworkNodeInfoService.invalidateRootNetworkNode(child.getIdNode(), rootNetworkUuid, InvalidateNodeTreeParameters.ALL));
invalidateNodeInfos.add(invalidateChildrenNodes(child.getIdNode(), rootNetworkUuid));
});
List<RootNetworkNodeInfoEntity> rootNetworkNodeInfoEntities = rootNetworkNodeInfoService.getRootNetworkNodes(rootNetworkUuid, getAllChildrenUuids(nodeUuid));

rootNetworkNodeInfoEntities.forEach(child ->
invalidateNodeInfos.add(rootNetworkNodeInfoService.invalidateRootNetworkNode(child, InvalidateNodeTreeParameters.ALL))
);

return invalidateNodeInfos;
}

Expand Down Expand Up @@ -1010,21 +1024,6 @@ public Boolean isReadOnly(UUID nodeUuid) {
return getNodeInfoEntity(nodeUuid).getReadOnly();
}

public List<UUID> getChildren(UUID id) {
List<UUID> children = new ArrayList<>();
doGetChildren(id, children);
return children;
}

private void doGetChildren(UUID id, List<UUID> children) {
Optional<NodeEntity> optNode = nodesRepository.findById(id);
optNode.ifPresent(node -> nodesRepository.findAllByParentNodeIdNode(id)
.forEach(child -> {
children.add(child.getIdNode());
doGetChildren(child.getIdNode(), children);
}));
}

// only used for tests
@Transactional
public UUID getParentNode(UUID nodeUuid, NodeType nodeType) {
Expand Down Expand Up @@ -1060,7 +1059,7 @@ private void fillIndexedNodeInfosToInvalidate(UUID parentNodeUuid, boolean inclu
if (includeParentNode) {
nodesToInvalidate.add(parentNodeUuid);
}
nodesToInvalidate.addAll(getChildren(parentNodeUuid));
nodesToInvalidate.addAll(getAllChildrenUuids(parentNodeUuid));
invalidateNodeInfos.addGroupUuids(
networkModificationNodeInfoRepository.findAllById(nodesToInvalidate).stream()
.map(NetworkModificationNodeInfoEntity::getModificationGroupUuid).toList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,10 @@ public void fillDeleteNodeInfo(UUID nodeUuid, DeleteNodeInfos deleteNodeInfos) {

public InvalidateNodeInfos invalidateRootNetworkNode(UUID nodeUuid, UUID rootNetworUuid, InvalidateNodeTreeParameters invalidateTreeParameters) {
RootNetworkNodeInfoEntity rootNetworkNodeInfoEntity = rootNetworkNodeInfoRepository.findByNodeInfoIdAndRootNetworkId(nodeUuid, rootNetworUuid).orElseThrow(() -> new StudyException(ROOT_NETWORK_NOT_FOUND));
return invalidateRootNetworkNode(rootNetworkNodeInfoEntity, invalidateTreeParameters);
}

public InvalidateNodeInfos invalidateRootNetworkNode(RootNetworkNodeInfoEntity rootNetworkNodeInfoEntity, InvalidateNodeTreeParameters invalidateTreeParameters) {
// No need to invalidate a node with a status different of "BUILT"
if (!rootNetworkNodeInfoEntity.getNodeBuildStatus().toDto().isBuilt()) {
return new InvalidateNodeInfos();
Expand Down Expand Up @@ -358,6 +361,10 @@ public List<UUID> getComputationResultUuids(UUID studyUuid, ComputationType comp
.toList();
}

public List<RootNetworkNodeInfoEntity> getRootNetworkNodes(UUID rootNetworkUuid, List<UUID> nodesUuids) {
return rootNetworkNodeInfoRepository.getAllByRootNetworkIdAndNodeInfoIdIn(rootNetworkUuid, nodesUuids);
}

public List<RootNetworkNodeInfoEntity> getAllByStudyUuidWithLoadFlowResultsNotNull(UUID studyUuid) {
return rootNetworkNodeInfoRepository.findAllByRootNetworkStudyIdAndLoadFlowResultUuidNotNull(studyUuid);
}
Expand Down
Loading