From d121d504e3f890bb15750f1aa695934b008aba6b Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Tue, 1 Oct 2024 10:37:49 +0200 Subject: [PATCH 1/4] BUGFIX: Improve performance on buildAncestorNodesQueries --- .../src/Domain/Repository/ContentSubgraph.php | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 810a095069f..9db3564434b 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -553,31 +553,33 @@ private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNod */ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId, FindAncestorNodesFilter|CountAncestorNodesFilter|FindClosestNodeFilter $filter): array { + // 1) Fetch the hierarchy relation of the initial node aggregate, where this node is stored as child node. + // The parent node anchor points to the first parent node of the given node aggregate. This is also used for + // the recursive part, to determine its ancestors. $queryBuilderInitial = $this->createQueryBuilder() - ->select('n.*, ph.subtreetags, ph.parentnodeanchor') + ->select('ph.subtreetags, ph.parentnodeanchor') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') - // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') - ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') - ->where('ch.contentstreamid = :contentStreamId') - ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'n.relationanchorpoint = ph.parentnodeanchor') + ->innerJoin('ph', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = ph.childnodeanchor') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial, 'ph'); - $this->addSubtreeTagConstraints($queryBuilderInitial, 'ch'); + // 2) Fetch the parent hierarchy recursive, starting with the anchor point of the resulting parent as child anchor + // point for the next iteration. $queryBuilderRecursive = $this->createQueryBuilder() - ->select('pn.*, h.subtreetags, h.parentnodeanchor') + ->select('h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') - ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = cn.parentnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint, 'ancestry', 'a'); + // 3) Finally we join the node table to all collected parent node anchor + $queryBuilderCte->innerJoin('a', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = a.parentnodeanchor'); + if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'pn'); } From bd2b9b9e5c346035ea0835b48df91832f9d3e64a Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Tue, 1 Oct 2024 11:55:18 +0200 Subject: [PATCH 2/4] BUGFIX: Improve performance on buildAncestorNodesQueries --- .../src/Domain/Repository/ContentSubgraph.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 9db3564434b..287240ab26b 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -558,8 +558,7 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId // the recursive part, to determine its ancestors. $queryBuilderInitial = $this->createQueryBuilder() ->select('ph.subtreetags, ph.parentnodeanchor') - ->from($this->nodeQueryBuilder->tableNames->node(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'n.relationanchorpoint = ph.parentnodeanchor') + ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph') ->innerJoin('ph', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = ph.childnodeanchor') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') From e02a7783576b49b913fcb8641b03e863ed5e0566 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Tue, 1 Oct 2024 15:10:37 +0200 Subject: [PATCH 3/4] BUGFIX: Improve performance on findClosestNode --- .../src/Domain/Repository/ContentSubgraph.php | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 287240ab26b..70fd9d3be97 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -355,27 +355,34 @@ public function countAncestorNodes(NodeAggregateId $entryNodeAggregateId, CountA public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClosestNodeFilter $filter): ?Node { + // 1) Fetch the hierarchy relation of the initial node aggregate, where this node is stored as child node. + // The parent node anchor points to the first parent node of the given node aggregate. This is also used for + // the recursive part, to determine its ancestors. $queryBuilderInitial = $this->createQueryBuilder() - ->select('n.*, ph.subtreetags, ph.parentnodeanchor') - ->from($this->nodeQueryBuilder->tableNames->node(), 'n') - // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->select('ph.subtreetags, ph.childnodeanchor, ph.parentnodeanchor') + ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph') + ->innerJoin('ph', $this->nodeQueryBuilder->tableNames->node(), 'n', 'n.relationanchorpoint = ph.childnodeanchor') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial, 'ph'); + // 2) Fetch the parent hierarchy recursive, starting with the anchor point of the resulting parent as child + // anchor point for the next iteration. $queryBuilderRecursive = $this->createQueryBuilder() - ->select('pn.*, h.subtreetags, h.parentnodeanchor') + ->select('h.subtreetags, h.childnodeanchor, h.parentnodeanchor') ->from('ancestry', 'cn') - ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = cn.parentnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint, 'ancestry', 'a'); + + // 3) Finally we join the node table to all collected child node anchor as we also need the starting node included for finding the "closest" node. + $queryBuilderCte->innerJoin('a', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = a.childnodeanchor'); $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'pn'); + $nodeRows = $this->fetchCteResults( $queryBuilderInitial, $queryBuilderRecursive, From 142551a30378d8d370682fd6757f698b5a1e5f68 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Tue, 1 Oct 2024 15:31:29 +0200 Subject: [PATCH 4/4] BUGFIX: Improve performance on buildAncestorNodesQueries --- .../src/Domain/Repository/ContentSubgraph.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 70fd9d3be97..c17841a428a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -564,7 +564,7 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId // The parent node anchor points to the first parent node of the given node aggregate. This is also used for // the recursive part, to determine its ancestors. $queryBuilderInitial = $this->createQueryBuilder() - ->select('ph.subtreetags, ph.parentnodeanchor') + ->select('ph.subtreetags, ph.parentnodeanchor, ph.childnodeanchor') ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph') ->innerJoin('ph', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = ph.childnodeanchor') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -575,7 +575,7 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId // 2) Fetch the parent hierarchy recursive, starting with the anchor point of the resulting parent as child anchor // point for the next iteration. $queryBuilderRecursive = $this->createQueryBuilder() - ->select('h.subtreetags, h.parentnodeanchor') + ->select('h.subtreetags, h.parentnodeanchor, h.childnodeanchor') ->from('ancestry', 'cn') ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = cn.parentnodeanchor') ->where('h.contentstreamid = :contentStreamId') @@ -584,7 +584,7 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint, 'ancestry', 'a'); // 3) Finally we join the node table to all collected parent node anchor - $queryBuilderCte->innerJoin('a', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = a.parentnodeanchor'); + $queryBuilderCte->innerJoin('a', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = a.childnodeanchor AND pn.nodeaggregateid <> :entryNodeAggregateId'); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'pn');