From 842e00b54848448e1d68a6f0ad1e5baa0ff6629a Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Wed, 1 May 2024 21:38:02 +0200 Subject: [PATCH 1/4] 4993 - Actually execute moveSubtreeTags tests and fix moveSubtreeTags --- .../Projection/Feature/SubtreeTagging.php | 11 ++++-- .../05-MoveNodeAggregate_SubtreeTags.feature | 34 +++++++++---------- .../Projection/ContentGraph/NodeTags.php | 2 +- .../Features/Bootstrap/ProjectedNodeTrait.php | 22 +++++++----- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 976526a10de..281e070e817 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -139,11 +139,14 @@ private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregate $nodeTags = $this->nodeTagsForNode($nodeAggregateId, $contentStreamId, $coveredDimensionSpacePoint); $newParentSubtreeTags = $this->nodeTagsForNode($newParentNodeAggregateId, $contentStreamId, $coveredDimensionSpacePoint); $newSubtreeTags = []; + $newDescendantSubtreeTags = []; foreach ($nodeTags->withoutInherited() as $tag) { $newSubtreeTags[$tag->value] = true; + $newDescendantSubtreeTags[$tag->value] = null; } foreach ($newParentSubtreeTags as $tag) { - $newSubtreeTags[$tag->value] = null; + $newSubtreeTags[$tag->value] ??= null; + $newDescendantSubtreeTags[$tag->value] = null; } if ($newSubtreeTags === [] && $nodeTags->isEmpty()) { return; @@ -151,7 +154,9 @@ private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregate $this->getDatabaseConnection()->executeStatement(' UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h SET h.subtreetags = JSON_MERGE_PATCH(:newParentTags, JSON_MERGE_PATCH(\'{}\', h.subtreetags)) - WHERE h.childnodeanchor IN ( + WHERE h.contentstreamid = :contentStreamId + AND h.dimensionspacepointhash = :dimensionSpacePointHash + AND h.childnodeanchor IN ( WITH RECURSIVE cte (id) AS ( SELECT ch.childnodeanchor FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation ch @@ -173,7 +178,7 @@ private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregate 'contentStreamId' => $contentStreamId->value, 'nodeAggregateId' => $nodeAggregateId->value, 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, - 'newParentTags' => json_encode($newSubtreeTags, JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), + 'newParentTags' => json_encode($newDescendantSubtreeTags, JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), ]); $this->getDatabaseConnection()->executeStatement(' UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature index eb7d9ed22e4..ea431988e7d 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature @@ -458,7 +458,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -469,7 +469,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -539,7 +539,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -609,7 +609,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -679,7 +679,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1020,7 +1020,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1031,7 +1031,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1101,7 +1101,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1171,7 +1171,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1241,7 +1241,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1788,7 +1788,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} @@ -1811,7 +1811,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -1881,7 +1881,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -1951,7 +1951,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "source"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -2021,7 +2021,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "source"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -2091,7 +2091,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -2161,7 +2161,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php index 8317643ab22..06794251a50 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php @@ -23,7 +23,7 @@ * * Internally, this consists of two collection: * - One for tags that are _explicitly_ set on the respective node. - * - And one that contains tags that are _inherited_ by one of the ancestor nodes + * - And one that contains tags that are only _inherited_ by one of the ancestor nodes and _not_ explicitly set * * In most cases, it shouldn't matter whether a tag is inherited or set explicitly. But sometimes the behavior is slightly different (e.g. the "disabled" checkbox in the Neos UI inspector is only checked if the node has the `disabled` tag set explicitly) * diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index bee6d1ffec9..05d64f2338e 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -243,23 +243,29 @@ public function iExpectTheNodeWithAggregateIdentifierToNotContainTheTag(string $ /** * @Then /^I expect this node to be exactly explicitly tagged "(.*)"$/ - * @param string $tagList the comma-separated list of tag names + * @param string $expectedTagList the comma-separated list of tag names */ - public function iExpectThisNodeToBeExactlyExplicitlyTagged(string $tagList): void + public function iExpectThisNodeToBeExactlyExplicitlyTagged(string $expectedTagList): void { - $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { - $currentNode->tags->withoutInherited()->toStringArray() === explode(',', $tagList); + $this->assertOnCurrentNode(function (Node $currentNode) use ($expectedTagList) { + Assert::assertSame( + ($expectedTagList === '') ? [] : explode(',', $expectedTagList), + $currentNode->tags->withoutInherited()->toStringArray() + ); }); } /** * @Then /^I expect this node to exactly inherit the tags "(.*)"$/ - * @param string $tagList the comma-separated list of tag names + * @param string $expectedTagList the comma-separated list of tag names */ - public function iExpectThisNodeToExactlyInheritTheTags(string $tagList): void + public function iExpectThisNodeToExactlyInheritTheTags(string $expectedTagList): void { - $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { - $currentNode->tags->onlyInherited()->toStringArray() === explode(',', $tagList); + $this->assertOnCurrentNode(function (Node $currentNode) use ($expectedTagList) { + Assert::assertSame( + ($expectedTagList === '') ? [] : explode(',', $expectedTagList), + $currentNode->tags->onlyInherited()->toStringArray(), + ); }); } From 9c54dc2968373c47d43f8beb3a20ff3c8fb521bb Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Thu, 2 May 2024 16:36:43 +0200 Subject: [PATCH 2/4] BUGFIX: Fix Subtree tagging CTE implementation and make tests more reliable by sorting tags --- .../Projection/Feature/SubtreeTagging.php | 90 +++++-------------- .../TagSubtree_WithoutDimensions.feature | 34 +++---- .../Features/Bootstrap/NodeTraversalTrait.php | 6 +- 3 files changed, 46 insertions(+), 84 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 281e070e817..56fd8440ffb 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -136,84 +136,42 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId, NodeAggregateId $newParentNodeAggregateId, DimensionSpacePoint $coveredDimensionSpacePoint): void { - $nodeTags = $this->nodeTagsForNode($nodeAggregateId, $contentStreamId, $coveredDimensionSpacePoint); - $newParentSubtreeTags = $this->nodeTagsForNode($newParentNodeAggregateId, $contentStreamId, $coveredDimensionSpacePoint); - $newSubtreeTags = []; - $newDescendantSubtreeTags = []; - foreach ($nodeTags->withoutInherited() as $tag) { - $newSubtreeTags[$tag->value] = true; - $newDescendantSubtreeTags[$tag->value] = null; - } - foreach ($newParentSubtreeTags as $tag) { - $newSubtreeTags[$tag->value] ??= null; - $newDescendantSubtreeTags[$tag->value] = null; - } - if ($newSubtreeTags === [] && $nodeTags->isEmpty()) { - return; - } $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h - SET h.subtreetags = JSON_MERGE_PATCH(:newParentTags, JSON_MERGE_PATCH(\'{}\', h.subtreetags)) - WHERE h.contentstreamid = :contentStreamId - AND h.dimensionspacepointhash = :dimensionSpacePointHash - AND h.childnodeanchor IN ( - WITH RECURSIVE cte (id) AS ( - SELECT ch.childnodeanchor - FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation ch - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = ch.parentnodeanchor - WHERE - n.nodeaggregateid = :nodeAggregateId - AND ch.contentstreamid = :contentStreamId - AND ch.dimensionspacepointhash = :dimensionSpacePointHash - UNION ALL + UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h, + ( + WITH RECURSIVE cte AS ( SELECT - dh.childnodeanchor + JSON_KEYS(th.subtreetags) subtreeTagsToInherit, th.childnodeanchor + FROM + ' . $this->getTableNamePrefix() . '_hierarchyrelation th + INNER JOIN ' . $this->getTableNamePrefix() . '_node tn ON tn.relationanchorpoint = th.childnodeanchor + WHERE + tn.nodeaggregateid = :nodeAggregateId + AND th.contentstreamid = :contentStreamId + AND th.dimensionspacepointhash = :dimensionSpacePointHash + UNION + SELECT JSON_MERGE(cte.subtreetagsToInherit, JSON_KEYS(JSON_MERGE_PATCH(\'{}\', dh.subtreetags))) subtreeTagsToInherit, dh.childnodeanchor FROM cte - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.id + JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.childnodeanchor ) - SELECT id FROM cte + SELECT * FROM cte + ) AS r + SET h.subtreetags = ( + SELECT + JSON_MERGE_PATCH(JSON_OBJECTAGG(htk.k, null), JSON_MERGE_PATCH(\'{}\', h.subtreetags)) + FROM + JSON_TABLE(r.subtreeTagsToInherit, \'$[*]\' COLUMNS (k VARCHAR(36) PATH \'$\')) htk ) - ', [ - 'contentStreamId' => $contentStreamId->value, - 'nodeAggregateId' => $nodeAggregateId->value, - 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, - 'newParentTags' => json_encode($newDescendantSubtreeTags, JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), - ]); - $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = h.childnodeanchor - SET h.subtreetags = :newParentTags WHERE - n.nodeaggregateid = :nodeAggregateId + h.childnodeanchor = r.childnodeanchor AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash - ', [ - 'contentStreamId' => $contentStreamId->value, - 'nodeAggregateId' => $nodeAggregateId->value, - 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, - 'newParentTags' => json_encode($newSubtreeTags, JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), - ]); - } - - private function nodeTagsForNode(NodeAggregateId $nodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): NodeTags - { - $subtreeTagsJson = $this->getDatabaseConnection()->fetchOne(' - SELECT h.subtreetags FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation h - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = h.childnodeanchor - WHERE - n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamid = :contentStreamId - AND h.dimensionspacepointhash = :dimensionSpacePointHash ', [ - 'nodeAggregateId' => $nodeAggregateId->value, 'contentStreamId' => $contentStreamId->value, - 'dimensionSpacePointHash' => $dimensionSpacePoint->hash, + 'nodeAggregateId' => $newParentNodeAggregateId->value, + 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, ]); - if (!is_string($subtreeTagsJson)) { - throw new \RuntimeException(sprintf('Failed to fetch SubtreeTags for node "%s" in content subgraph "%s@%s"', $nodeAggregateId->value, $dimensionSpacePoint->toJson(), $contentStreamId->value), 1698838865); - } - return NodeFactory::extractNodeTagsFromJson($subtreeTagsJson); } private function subtreeTagsForHierarchyRelation(ContentStreamId $contentStreamId, NodeRelationAnchorPoint $parentNodeAnchorPoint, DimensionSpacePoint $dimensionSpacePoint): NodeTags diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature index c88e62f53b3..6dc797cfa53 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature @@ -173,11 +173,11 @@ Feature: Tag subtree without dimensions """ b (tag2*) b1 (tag3*,tag2) - a1a (tag4*,tag3,tag2) - a1a1 (tag4*,tag1*,tag3,tag2) - a1a1a (tag4*,tag3,tag2) - a1a1b (tag4*,tag3,tag2) - a1a2 (tag4*,tag3,tag2) + a1a (tag4*,tag2,tag3) + a1a1 (tag1*,tag2,tag3,tag4) + a1a1a (tag1,tag2,tag3,tag4) + a1a1b (tag1,tag2,tag3,tag4) + a1a2 (tag2,tag3,tag4) """ When the command CreateNodeAggregateWithNode is executed with payload: @@ -189,12 +189,12 @@ Feature: Tag subtree without dimensions """ b (tag2*) b1 (tag3*,tag2) - a1a (tag4*,tag3,tag2) - a1a1 (tag4*,tag1*,tag3,tag2) - a1a1a (tag4*,tag3,tag2) - a1a1b (tag4*,tag3,tag2) - a1a2 (tag4*,tag3,tag2) - a1a3 (tag4,tag3,tag2) + a1a (tag4*,tag2,tag3) + a1a1 (tag1*,tag2,tag3,tag4) + a1a1a (tag1,tag2,tag3,tag4) + a1a1b (tag1,tag2,tag3,tag4) + a1a2 (tag2,tag3,tag4) + a1a3 (tag2,tag3,tag4) """ When the command UntagSubtree is executed with payload: @@ -206,10 +206,10 @@ Feature: Tag subtree without dimensions """ b (tag2*) b1 (tag3*,tag2) - a1a (tag3,tag2) - a1a1 (tag4*,tag1*,tag3,tag2) - a1a1a (tag4*,tag3,tag2) - a1a1b (tag4*,tag3,tag2) - a1a2 (tag4*,tag3,tag2) - a1a3 (tag3,tag2) + a1a (tag2,tag3) + a1a1 (tag1*,tag2,tag3) + a1a1a (tag1,tag2,tag3) + a1a1b (tag1,tag2,tag3) + a1a2 (tag2,tag3) + a1a3 (tag2,tag3) """ diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php index 119ed394de0..098598b9c2a 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php @@ -248,7 +248,11 @@ public function iExecuteTheFindSubtreeQueryIExpectTheFollowingTrees(string $entr $subtree = array_shift($subtreeStack); $tags = []; if ($withTags !== null) { - $tags = [...array_map(static fn(string $tag) => $tag . '*', $subtree->node->tags->withoutInherited()->toStringArray()), ...$subtree->node->tags->onlyInherited()->toStringArray()]; + $explicitTags = $subtree->node->tags->withoutInherited()->toStringArray(); + sort($explicitTags); + $inheritedTags = $subtree->node->tags->onlyInherited()->toStringArray(); + sort($inheritedTags); + $tags = [...array_map(static fn(string $tag) => $tag . '*', $explicitTags), ...$inheritedTags]; } $result[] = str_repeat(' ', $subtree->level) . $subtree->node->nodeAggregateId->value . ($tags !== [] ? ' (' . implode(',', $tags) . ')' : ''); $subtreeStack = [...$subtree->children, ...$subtreeStack]; From 097918f5bdc0842858909524321b7862be54b119 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sun, 5 May 2024 15:43:47 +0200 Subject: [PATCH 3/4] 4993 - Adjust subtree tag moval CTE to pass the tests --- .../Domain/Projection/Feature/NodeMove.php | 1 - .../Projection/Feature/SubtreeTagging.php | 33 +++++++++++++++---- .../Features/Bootstrap/ProjectedNodeTrait.php | 8 +++-- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php index fbd88417383..fdf61f7eceb 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php @@ -56,7 +56,6 @@ private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event): void ); $this->moveSubtreeTags( $event->contentStreamId, - $event->nodeAggregateId, $event->newParentNodeAggregateId, $succeedingSiblingForCoverage->dimensionSpacePoint ); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 56fd8440ffb..7e0cbc2564a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -134,8 +134,11 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void ]); } - private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId, NodeAggregateId $newParentNodeAggregateId, DimensionSpacePoint $coveredDimensionSpacePoint): void - { + private function moveSubtreeTags( + ContentStreamId $contentStreamId, + NodeAggregateId $newParentNodeAggregateId, + DimensionSpacePoint $coveredDimensionSpacePoint + ): void { $this->getDatabaseConnection()->executeStatement(' UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h, ( @@ -146,20 +149,36 @@ private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregate ' . $this->getTableNamePrefix() . '_hierarchyrelation th INNER JOIN ' . $this->getTableNamePrefix() . '_node tn ON tn.relationanchorpoint = th.childnodeanchor WHERE - tn.nodeaggregateid = :nodeAggregateId + tn.nodeaggregateid = :newParentNodeAggregateId AND th.contentstreamid = :contentStreamId AND th.dimensionspacepointhash = :dimensionSpacePointHash UNION - SELECT JSON_MERGE(cte.subtreetagsToInherit, JSON_KEYS(JSON_MERGE_PATCH(\'{}\', dh.subtreetags))) subtreeTagsToInherit, dh.childnodeanchor + SELECT + JSON_MERGE_PRESERVE( + cte.subtreeTagsToInherit, + JSON_KEYS(JSON_MERGE_PATCH( + \'{}\', + dh.subtreetags + )) + ) subtreeTagsToInherit, + dh.childnodeanchor FROM cte - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.childnodeanchor + JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh + ON + dh.parentnodeanchor = cte.childnodeanchor + WHERE + dh.contentstreamid = :contentStreamId + AND dh.dimensionspacepointhash = :dimensionSpacePointHash ) SELECT * FROM cte ) AS r SET h.subtreetags = ( SELECT - JSON_MERGE_PATCH(JSON_OBJECTAGG(htk.k, null), JSON_MERGE_PATCH(\'{}\', h.subtreetags)) + JSON_MERGE_PATCH( + IFNULL(JSON_OBJECTAGG(htk.k, null), \'{}\'), + JSON_MERGE_PATCH(\'{}\', h.subtreetags) + ) FROM JSON_TABLE(r.subtreeTagsToInherit, \'$[*]\' COLUMNS (k VARCHAR(36) PATH \'$\')) htk ) @@ -169,7 +188,7 @@ private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregate AND h.dimensionspacepointhash = :dimensionSpacePointHash ', [ 'contentStreamId' => $contentStreamId->value, - 'nodeAggregateId' => $newParentNodeAggregateId->value, + 'newParentNodeAggregateId' => $newParentNodeAggregateId->value, 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, ]); } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index 05d64f2338e..5b6d14e9c82 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -248,9 +248,11 @@ public function iExpectTheNodeWithAggregateIdentifierToNotContainTheTag(string $ public function iExpectThisNodeToBeExactlyExplicitlyTagged(string $expectedTagList): void { $this->assertOnCurrentNode(function (Node $currentNode) use ($expectedTagList) { + $actualTags = $currentNode->tags->withoutInherited()->toStringArray(); + sort($actualTags); Assert::assertSame( ($expectedTagList === '') ? [] : explode(',', $expectedTagList), - $currentNode->tags->withoutInherited()->toStringArray() + $actualTags ); }); } @@ -262,9 +264,11 @@ public function iExpectThisNodeToBeExactlyExplicitlyTagged(string $expectedTagLi public function iExpectThisNodeToExactlyInheritTheTags(string $expectedTagList): void { $this->assertOnCurrentNode(function (Node $currentNode) use ($expectedTagList) { + $actualTags = $currentNode->tags->onlyInherited()->toStringArray(); + sort($actualTags); Assert::assertSame( ($expectedTagList === '') ? [] : explode(',', $expectedTagList), - $currentNode->tags->onlyInherited()->toStringArray(), + $actualTags, ); }); } From 145458807738ab2abd39322b3853662441931891 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 6 May 2024 19:11:06 +0200 Subject: [PATCH 4/4] 4993 - Adjust descendant hierarchy join --- .../src/Domain/Projection/Feature/SubtreeTagging.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 7e0cbc2564a..25887ab2575 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -167,8 +167,7 @@ private function moveSubtreeTags( JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.childnodeanchor - WHERE - dh.contentstreamid = :contentStreamId + AND dh.contentstreamid = :contentStreamId AND dh.dimensionspacepointhash = :dimensionSpacePointHash ) SELECT * FROM cte