From 1ee4ed2ddea1df7c8b57ae21edf3e4e4164183c3 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:38:56 +0200 Subject: [PATCH 01/15] TASK: Add docs to `CacheFlushingStrategy` --- Neos.Neos/Classes/Fusion/Cache/CacheFlushingStrategy.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Neos.Neos/Classes/Fusion/Cache/CacheFlushingStrategy.php b/Neos.Neos/Classes/Fusion/Cache/CacheFlushingStrategy.php index 2584f4fd717..d57f2f2694f 100644 --- a/Neos.Neos/Classes/Fusion/Cache/CacheFlushingStrategy.php +++ b/Neos.Neos/Classes/Fusion/Cache/CacheFlushingStrategy.php @@ -6,6 +6,12 @@ enum CacheFlushingStrategy { + /** + * All content changes in the content repository are persisted immediately and thus an immediate flush is also required. + */ case IMMEDIATE; + /** + * Changes like from assets (changing a title) will only be persisted when flow is shutting down. Thus we delay the flush as well. + */ case ON_SHUTDOWN; } From 4db7389fc821e6cc7dd2e9afb124eaae45ec7a9c Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:40:23 +0200 Subject: [PATCH 02/15] TASK: introduce `ContentGraphInterface::findAncestorNodeAggregateIds` --- .../src/Domain/Repository/ContentGraph.php | 23 +++++++++++++ .../Domain/Repository/ContentHypergraph.php | 23 +++++++++++++ .../ContentGraph/ContentGraphInterface.php | 8 +++++ .../AssetChangeHandlerForCacheFlushing.php | 24 ++------------ ...phProjectorCatchUpHookForCacheFlushing.php | 33 +++++-------------- 5 files changed, 66 insertions(+), 45 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index b0d9193364b..85572eaf35b 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -35,6 +35,7 @@ use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -188,6 +189,28 @@ public function findParentNodeAggregates( return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } + public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregateId): NodeAggregateIds + { + $stack = iterator_to_array($this->findParentNodeAggregates($entryNodeAggregateId)); + + $ancestorNodeAggregateIds = []; + while ($stack !== []) { + $nodeAggregate = array_shift($stack); + + // Prevent infinite loops + // NOTE: Normally, the content graph cannot contain cycles. However, during the + // testcase "Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature" + // and in case of bugs, it could have actually cycles. + // The content cache catchup hook leverage this method and would otherwise be hanging up in an endless loop. + // That's why we track the seen NodeAggregateIds to be sure we don't travers them multiple times. + if (!in_array($nodeAggregate->nodeAggregateId, $ancestorNodeAggregateIds, false)) { + $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; + array_push($stack, ...iterator_to_array($this->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); + } + } + return NodeAggregateIds::fromArray($ancestorNodeAggregateIds); + } + public function findChildNodeAggregates( NodeAggregateId $parentNodeAggregateId ): NodeAggregates { diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php index cfab1b47fb8..8c97ac3eebc 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php @@ -32,6 +32,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -183,6 +184,28 @@ public function findParentNodeAggregates( ); } + public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregateId): NodeAggregateIds + { + $stack = iterator_to_array($this->findParentNodeAggregates($entryNodeAggregateId)); + + $ancestorNodeAggregateIds = []; + while ($stack !== []) { + $nodeAggregate = array_shift($stack); + + // Prevent infinite loops + // NOTE: Normally, the content graph cannot contain cycles. However, during the + // testcase "Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature" + // and in case of bugs, it could have actually cycles. + // The content cache catchup hook leverage this method and would otherwise be hanging up in an endless loop. + // That's why we track the seen NodeAggregateIds to be sure we don't travers them multiple times. + if (!in_array($nodeAggregate->nodeAggregateId, $ancestorNodeAggregateIds, false)) { + $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; + array_push($stack, ...iterator_to_array($this->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); + } + } + return NodeAggregateIds::fromArray($ancestorNodeAggregateIds); + } + public function findChildNodeAggregates( NodeAggregateId $parentNodeAggregateId ): NodeAggregates { diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 67af3f6d5ff..79b35b0bddf 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -23,6 +23,7 @@ use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -108,6 +109,13 @@ public function findParentNodeAggregates( NodeAggregateId $childNodeAggregateId ): NodeAggregates; + /** + * @internal + */ + public function findAncestorNodeAggregateIds( + NodeAggregateId $entryNodeAggregateId + ): NodeAggregateIds; + /** * @internal only for consumption inside the Command Handler */ diff --git a/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php index e17c2b685b4..4cfce8948a4 100644 --- a/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php +++ b/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php @@ -60,7 +60,8 @@ public function registerAssetChange(AssetInterface $asset): void } // - $nodeAggregate = $contentRepository->getContentGraph($workspaceName)->findNodeAggregateById($usage->nodeAggregateId); + $contentGraph = $contentRepository->getContentGraph($workspaceName); + $nodeAggregate = $contentGraph->findNodeAggregateById($usage->nodeAggregateId); if ($nodeAggregate === null) { continue; } @@ -69,30 +70,11 @@ public function registerAssetChange(AssetInterface $asset): void $workspaceName, $nodeAggregate->nodeAggregateId, $nodeAggregate->nodeTypeName, - $this->determineAncestorNodeAggregateIds($contentRepository, $workspaceName, $nodeAggregate->nodeAggregateId), + $contentGraph->findAncestorNodeAggregateIds($nodeAggregate->nodeAggregateId), ); $this->contentCacheFlusher->flushNodeAggregate($flushNodeAggregateRequest, CacheFlushingStrategy::ON_SHUTDOWN); } } } - - private function determineAncestorNodeAggregateIds(ContentRepository $contentRepository, WorkspaceName $workspaceName, NodeAggregateId $childNodeAggregateId): NodeAggregateIds - { - $contentGraph = $contentRepository->getContentGraph($workspaceName); - $stack = iterator_to_array($contentGraph->findParentNodeAggregates($childNodeAggregateId)); - - $ancestorNodeAggregateIds = []; - while ($stack !== []) { - $nodeAggregate = array_shift($stack); - - // Prevent infinite loops - if (!in_array($nodeAggregate->nodeAggregateId, $ancestorNodeAggregateIds, false)) { - $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; - array_push($stack, ...iterator_to_array($contentGraph->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); - } - } - - return NodeAggregateIds::fromArray($ancestorNodeAggregateIds); - } } diff --git a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php index 321f6308afc..ec79ed487ce 100644 --- a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php +++ b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php @@ -167,7 +167,8 @@ public function onBeforeEvent(EventInterface $eventInstance, EventEnvelope $even $this->scheduleCacheFlushJobForNodeAggregate( $this->contentRepository, $eventInstance->workspaceName, - $nodeAggregate + $nodeAggregate, + $contentGraph->findAncestorNodeAggregateIds($eventInstance->getNodeAggregateId()), ); } } @@ -197,7 +198,8 @@ public function onAfterEvent(EventInterface $eventInstance, EventEnvelope $event && $eventInstance instanceof EmbedsContentStreamId && $eventInstance instanceof EmbedsWorkspaceName ) { - $nodeAggregate = $this->contentRepository->getContentGraph($eventInstance->getWorkspaceName())->findNodeAggregateById( + $contentGraph = $this->contentRepository->getContentGraph($eventInstance->getWorkspaceName()); + $nodeAggregate = $contentGraph->findNodeAggregateById( $eventInstance->getNodeAggregateId() ); @@ -205,7 +207,8 @@ public function onAfterEvent(EventInterface $eventInstance, EventEnvelope $event $this->scheduleCacheFlushJobForNodeAggregate( $this->contentRepository, $eventInstance->getWorkspaceName(), - $nodeAggregate + $nodeAggregate, + $contentGraph->findAncestorNodeAggregateIds($eventInstance->getNodeAggregateId()) ); } } @@ -214,7 +217,8 @@ public function onAfterEvent(EventInterface $eventInstance, EventEnvelope $event private function scheduleCacheFlushJobForNodeAggregate( ContentRepository $contentRepository, WorkspaceName $workspaceName, - NodeAggregate $nodeAggregate + NodeAggregate $nodeAggregate, + NodeAggregateIds $ancestorNodeAggregateIds ): void { // we store this in an associative array deduplicate. $this->flushNodeAggregateRequestsOnAfterCatchUp[$workspaceName->value . '__' . $nodeAggregate->nodeAggregateId->value] = FlushNodeAggregateRequest::create( @@ -222,7 +226,7 @@ private function scheduleCacheFlushJobForNodeAggregate( $workspaceName, $nodeAggregate->nodeAggregateId, $nodeAggregate->nodeTypeName, - $this->determineAncestorNodeAggregateIds($workspaceName, $nodeAggregate->nodeAggregateId) + $ancestorNodeAggregateIds ); } @@ -237,25 +241,6 @@ private function scheduleCacheFlushJobForWorkspaceName( ); } - private function determineAncestorNodeAggregateIds(WorkspaceName $workspaceName, NodeAggregateId $childNodeAggregateId): NodeAggregateIds - { - $contentGraph = $this->contentRepository->getContentGraph($workspaceName); - $stack = iterator_to_array($contentGraph->findParentNodeAggregates($childNodeAggregateId)); - - $ancestorNodeAggregateIds = []; - while ($stack !== []) { - $nodeAggregate = array_shift($stack); - - // Prevent infinite loops - if (!in_array($nodeAggregate->nodeAggregateId, $ancestorNodeAggregateIds, false)) { - $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; - array_push($stack, ...iterator_to_array($contentGraph->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); - } - } - - return NodeAggregateIds::fromArray($ancestorNodeAggregateIds); - } - public function onBeforeBatchCompleted(): void { } From 127afd07137db89e30e2d411299789a9dbb447a8 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:46:09 +0200 Subject: [PATCH 03/15] WIP: remove hacky loop detection in `findAncestorNodeAggregateIds` by applying event in test directly --- ...ectionIntegrityViolationDetectionTrait.php | 48 +++++++++++++++++++ .../src/Domain/Repository/ContentGraph.php | 13 +---- .../Domain/Repository/ContentHypergraph.php | 13 +---- ...AreConnectedToARootNodePerSubgraph.feature | 3 +- ...ricCommandExecutionAndEventPublication.php | 2 +- 5 files changed, 55 insertions(+), 24 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php index a3af95d66c0..9144cb4d8e7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php @@ -19,17 +19,24 @@ use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Exception\InvalidArgumentException; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; +use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Tests\Behavior\Features\Bootstrap\Helpers\TestingNodeAggregateId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; +use Neos\ContentRepository\Core\EventStore\EventNormalizer; +use Neos\ContentRepository\Core\EventStore\EventPersister; +use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; +use Neos\ContentRepository\Core\Projection\Projections; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\CRTestSuiteRuntimeVariables; use Neos\Error\Messages\Error; use Neos\Error\Messages\Result; +use Neos\EventStore\Model\Event; +use Neos\EventStore\Model\EventEnvelope; use PHPUnit\Framework\Assert; /** @@ -344,4 +351,45 @@ public function iExpectIntegrityViolationDetectionResultErrorNumberNToHaveCodeX( $error->getCode() ); } + + /** + * @Given /^the event NodeAggregateWasMoved is hacky directly applied with payload:$/ + * @param TableNode $payloadTable + * @throws \Exception + */ + public function applyNodeAggregateWasMoved(TableNode $payloadTable) + { + $eventPayload = $this->readPayloadTable($payloadTable); + $contentStreamId = ContentStreamId::fromString($eventPayload['contentStreamId']); + $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId); + $eventType = 'NodeAggregateWasMoved'; + + $artificiallyConstructedEvent = new Event( + Event\EventId::create(), + Event\EventType::fromString($eventType), + Event\EventData::fromString(json_encode($eventPayload)), + Event\EventMetadata::fromArray([]) + ); + /** @var EventPersister $eventPersister */ + $eventPersister = (new \ReflectionClass($this->currentContentRepository))->getProperty('eventPersister') + ->getValue($this->currentContentRepository); + /** @var EventNormalizer $eventNormalizer */ + $eventNormalizer = (new \ReflectionClass($eventPersister))->getProperty('eventNormalizer') + ->getValue($eventPersister); + /** @var Projections $projections */ + $projections = (new \ReflectionClass($eventPersister))->getProperty('projections') + ->getValue($eventPersister); + + $event = $eventNormalizer->denormalize($artificiallyConstructedEvent); + + $envelope = new EventEnvelope( + $artificiallyConstructedEvent, + $streamName->getEventStreamName(), + Event\Version::first(), + Event\SequenceNumber::none(), + new \DateTimeImmutable() + ); + + $projections->get(DoctrineDbalContentGraphProjection::class)->apply($event, $envelope); + } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 85572eaf35b..178844f3f63 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -196,17 +196,8 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate $ancestorNodeAggregateIds = []; while ($stack !== []) { $nodeAggregate = array_shift($stack); - - // Prevent infinite loops - // NOTE: Normally, the content graph cannot contain cycles. However, during the - // testcase "Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature" - // and in case of bugs, it could have actually cycles. - // The content cache catchup hook leverage this method and would otherwise be hanging up in an endless loop. - // That's why we track the seen NodeAggregateIds to be sure we don't travers them multiple times. - if (!in_array($nodeAggregate->nodeAggregateId, $ancestorNodeAggregateIds, false)) { - $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; - array_push($stack, ...iterator_to_array($this->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); - } + $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; + array_push($stack, ...iterator_to_array($this->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); } return NodeAggregateIds::fromArray($ancestorNodeAggregateIds); } diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php index 8c97ac3eebc..3f48750e3fc 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php @@ -191,17 +191,8 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate $ancestorNodeAggregateIds = []; while ($stack !== []) { $nodeAggregate = array_shift($stack); - - // Prevent infinite loops - // NOTE: Normally, the content graph cannot contain cycles. However, during the - // testcase "Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature" - // and in case of bugs, it could have actually cycles. - // The content cache catchup hook leverage this method and would otherwise be hanging up in an endless loop. - // That's why we track the seen NodeAggregateIds to be sure we don't travers them multiple times. - if (!in_array($nodeAggregate->nodeAggregateId, $ancestorNodeAggregateIds, false)) { - $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; - array_push($stack, ...iterator_to_array($this->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); - } + $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; + array_push($stack, ...iterator_to_array($this->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); } return NodeAggregateIds::fromArray($ancestorNodeAggregateIds); } diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature index cbf6e2c55b3..b37843a6eb3 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature @@ -52,7 +52,8 @@ Feature: Run projection integrity violation detection regarding root connection | parentNodeAggregateId | "sir-david-nodenborough" | | nodeName | "child-document" | | nodeAggregateClassification | "regular" | - And the event NodeAggregateWasMoved was published with payload: + # we must apply the event directly as the state is so corrupt that catchup hooks might fail like the content cache flusher + And the event NodeAggregateWasMoved is hacky directly applied with payload: | Key | Value | | workspaceName | "live" | | contentStreamId | "cs-identifier" | diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php index c8e2118d5e4..f4b58bffb20 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php @@ -135,7 +135,7 @@ protected function publishEvent(string $eventType, StreamName $streamName, array /** @var EventPersister $eventPersister */ $eventPersister = (new \ReflectionClass($this->currentContentRepository))->getProperty('eventPersister') ->getValue($this->currentContentRepository); - /** @var EventNormalizer $eventPersister */ + /** @var EventNormalizer $eventNormalizer */ $eventNormalizer = (new \ReflectionClass($eventPersister))->getProperty('eventNormalizer') ->getValue($eventPersister); $event = $eventNormalizer->denormalize($artificiallyConstructedEvent); From 18ea4baa8ee2e48e84acca83e3c7214000496bd3 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:38:29 +0200 Subject: [PATCH 04/15] TASK: Remove apply event hack and change hierarchy relation's parent manually --- ...ectionIntegrityViolationDetectionTrait.php | 73 +++++++------------ ...AreConnectedToARootNodePerSubgraph.feature | 29 +++++--- 2 files changed, 45 insertions(+), 57 deletions(-) rename {Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features => Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection}/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature (73%) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php index 9144cb4d8e7..1ccdb4a01a0 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php @@ -19,24 +19,17 @@ use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Exception\InvalidArgumentException; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; -use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Tests\Behavior\Features\Bootstrap\Helpers\TestingNodeAggregateId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; -use Neos\ContentRepository\Core\EventStore\EventNormalizer; -use Neos\ContentRepository\Core\EventStore\EventPersister; -use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; -use Neos\ContentRepository\Core\Projection\Projections; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\CRTestSuiteRuntimeVariables; use Neos\Error\Messages\Error; use Neos\Error\Messages\Result; -use Neos\EventStore\Model\Event; -use Neos\EventStore\Model\EventEnvelope; use PHPUnit\Framework\Assert; /** @@ -110,6 +103,31 @@ public function iAddTheFollowingHierarchyRelation(TableNode $payloadTable): void ); } + /** + * @When /^I change the following hierarchy relation's parent:$/ + * @throws DBALException + */ + public function iChangeTheFollowingHierarchyRelationsParent(TableNode $payloadTable): void + { + $dataset = $this->transformPayloadTableToDataset($payloadTable); + $record = $this->transformDatasetToHierarchyRelationRecord($dataset); + unset($record['position']); + + $newParentHierarchyRelation = $this->findHierarchyRelationByIds( + ContentStreamId::fromString($dataset['contentStreamId']), + DimensionSpacePoint::fromArray($dataset['dimensionSpacePoint']), + NodeAggregateId::fromString($dataset['newParentNodeAggregateId']) + ); + + $this->dbal->update( + $this->tableNames()->hierarchyRelation(), + [ + 'parentnodeanchor' => $newParentHierarchyRelation['childnodeanchor'] + ], + $record + ); + } + /** * @When /^I change the following hierarchy relation's dimension space point hash:$/ * @param TableNode $payloadTable @@ -351,45 +369,4 @@ public function iExpectIntegrityViolationDetectionResultErrorNumberNToHaveCodeX( $error->getCode() ); } - - /** - * @Given /^the event NodeAggregateWasMoved is hacky directly applied with payload:$/ - * @param TableNode $payloadTable - * @throws \Exception - */ - public function applyNodeAggregateWasMoved(TableNode $payloadTable) - { - $eventPayload = $this->readPayloadTable($payloadTable); - $contentStreamId = ContentStreamId::fromString($eventPayload['contentStreamId']); - $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId); - $eventType = 'NodeAggregateWasMoved'; - - $artificiallyConstructedEvent = new Event( - Event\EventId::create(), - Event\EventType::fromString($eventType), - Event\EventData::fromString(json_encode($eventPayload)), - Event\EventMetadata::fromArray([]) - ); - /** @var EventPersister $eventPersister */ - $eventPersister = (new \ReflectionClass($this->currentContentRepository))->getProperty('eventPersister') - ->getValue($this->currentContentRepository); - /** @var EventNormalizer $eventNormalizer */ - $eventNormalizer = (new \ReflectionClass($eventPersister))->getProperty('eventNormalizer') - ->getValue($eventPersister); - /** @var Projections $projections */ - $projections = (new \ReflectionClass($eventPersister))->getProperty('projections') - ->getValue($eventPersister); - - $event = $eventNormalizer->denormalize($artificiallyConstructedEvent); - - $envelope = new EventEnvelope( - $artificiallyConstructedEvent, - $streamName->getEventStreamName(), - Event\Version::first(), - Event\SequenceNumber::none(), - new \DateTimeImmutable() - ); - - $projections->get(DoctrineDbalContentGraphProjection::class)->apply($event, $envelope); - } } diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature similarity index 73% rename from Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature rename to Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature index b37843a6eb3..1ef592ec8f5 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature @@ -52,16 +52,27 @@ Feature: Run projection integrity violation detection regarding root connection | parentNodeAggregateId | "sir-david-nodenborough" | | nodeName | "child-document" | | nodeAggregateClassification | "regular" | - # we must apply the event directly as the state is so corrupt that catchup hooks might fail like the content cache flusher - And the event NodeAggregateWasMoved is hacky directly applied with payload: - | Key | Value | - | workspaceName | "live" | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | newParentNodeAggregateId | "nody-mc-nodeface" | - | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"language":"de"},"nodeAggregateId": null},{"dimensionSpacePoint":{"language":"gsw"},"nodeAggregateId": null}] | + + When I change the following hierarchy relation's parent: + | Key | Value | + | contentStreamId | "cs-identifier" | + | dimensionSpacePoint | {"language":"de"} | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | childNodeAggregateId | "sir-david-nodenborough" | + | newParentNodeAggregateId | "nody-mc-nodeface" | + And I run integrity violation detection + Then I expect the integrity violation detection result to contain exactly 1 errors + And I expect integrity violation detection result error number 1 to have code 1597754245 + + # Another error. One error per subgraph + When I change the following hierarchy relation's parent: + | Key | Value | + | contentStreamId | "cs-identifier" | + | dimensionSpacePoint | {"language":"gsw"} | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | childNodeAggregateId | "sir-david-nodenborough" | + | newParentNodeAggregateId | "nody-mc-nodeface" | And I run integrity violation detection - # one error per subgraph Then I expect the integrity violation detection result to contain exactly 2 errors And I expect integrity violation detection result error number 1 to have code 1597754245 And I expect integrity violation detection result error number 2 to have code 1597754245 From c01a83f4624a5b7591ee89b47c6d2be1e0045aa5 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:43:11 +0200 Subject: [PATCH 05/15] TASK: Prefer commands instead publishing events directly --- ...NodesAreConnectedToARootNodePerSubgraph.feature | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature index 1ef592ec8f5..7981a4ef09d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature @@ -10,6 +10,9 @@ Feature: Run projection integrity violation detection regarding root connection | language | de, gsw | gsw->de | And using the following node types: """yaml + 'Neos.ContentRepository.Testing:Root': + superTypes: + 'Neos.ContentRepository:Root': true 'Neos.ContentRepository.Testing:Document': [] """ And using identifier "default", I define a content repository @@ -22,18 +25,16 @@ Feature: Run projection integrity violation detection regarding root connection | newContentStreamId | "cs-identifier" | Scenario: Create a cycle - When the event RootNodeAggregateWithNodeWasCreated was published with payload: + When the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | workspaceName | "live" | - | contentStreamId | "cs-identifier" | | nodeAggregateId | "lady-eleonode-rootford" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | + | nodeTypeName | "Neos.ContentRepository.Testing:Root" | | coveredDimensionSpacePoints | [{"language":"de"},{"language":"gsw"}] | | nodeAggregateClassification | "root" | - When the event NodeAggregateWithNodeWasCreated was published with payload: + When the command CreateNodeAggregateWithNode is executed with payload: | Key | Value | | workspaceName | "live" | - | contentStreamId | "cs-identifier" | | nodeAggregateId | "sir-david-nodenborough" | | nodeTypeName | "Neos.ContentRepository.Testing:Document" | | originDimensionSpacePoint | {"language":"de"} | @@ -41,10 +42,9 @@ Feature: Run projection integrity violation detection regarding root connection | parentNodeAggregateId | "lady-eleonode-rootford" | | nodeName | "document" | | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: + When the command CreateNodeAggregateWithNode is executed with payload: | Key | Value | | workspaceName | "live" | - | contentStreamId | "cs-identifier" | | nodeAggregateId | "nody-mc-nodeface" | | nodeTypeName | "Neos.ContentRepository.Testing:Document" | | originDimensionSpacePoint | {"language":"de"} | From 1a644eee0b0d17ccdb87e2025ac87f73204ffa2f Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:20:29 +0200 Subject: [PATCH 06/15] TASK: Add behat test for `findAncestorNodeAggregateIds` --- .../NodeTraversal/AncestorNodes.feature | 17 ++++++++++++++++- .../Bootstrap/CRTestSuiteRuntimeVariables.php | 4 +--- .../Features/Bootstrap/NodeTraversalTrait.php | 12 ++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature index 3855686d40e..3bfeb899b64 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature @@ -1,6 +1,6 @@ @contentrepository @adapters=DoctrineDBAL # TODO implement for Postgres -Feature: Find and count nodes using the findAncestorNodes and countAncestorNodes queries +Feature: Find and count nodes using the findAncestorNodes, countAncestorNodes and findAncestorNodeAggregateIds queries Background: Given using the following content dimensions: @@ -79,6 +79,7 @@ Feature: Find and count nodes using the findAncestorNodes and countAncestorNodes | nodeVariantSelectionStrategy | "allVariants" | Scenario: + Subgraph queries # findAncestorNodes queries without results When I execute the findAncestorNodes query for entry node aggregate id "non-existing" I expect no nodes to be returned # a2a2a is disabled @@ -89,3 +90,17 @@ Feature: Find and count nodes using the findAncestorNodes and countAncestorNodes # findAncestorNodes queries with results When I execute the findAncestorNodes query for entry node aggregate id "a2a2b" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned and the total count to be 6 When I execute the findAncestorNodes query for entry node aggregate id "a2a2b" and filter '{"nodeTypes": "Neos.ContentRepository.Testing:Page"}' I expect the nodes "a2a2,a2,a" to be returned and the total count to be 3 + + Scenario: + Contentgraph queries + # findAncestorNodes queries without results + When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "non-existing" I expect no nodes to be returned + + # findAncestorNodes queries with results + # a2a2a is disabled + When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2a" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned + # a2b is disabled + When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2b1" I expect the nodes "a2b,a2,a,home,lady-eleonode-rootford" to be returned + + # findAncestorNodes queries with results + When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2b" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index a9520df0c93..2cc0a40d7ae 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php @@ -125,9 +125,7 @@ public function visibilityConstraintsAreSetTo(string $restrictionType): void public function getCurrentSubgraph(): ContentSubgraphInterface { - $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); - - return $contentGraphFinder->getByWorkspaceName($this->currentWorkspaceName)->getSubgraph( + return $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, $this->currentVisibilityConstraints ); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php index 46b55f80fef..4a0a25aad9b 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php @@ -293,6 +293,18 @@ public function iExecuteTheFindAncestorNodesQueryIExpectTheFollowingNodes(string Assert::assertSame($expectedTotalCount ?? count($expectedNodeIds), $actualCount, 'countAncestorNodes returned an unexpected result'); } + /** + * @When /^I execute the findAncestorNodeAggregateIds query for entry node aggregate id "(?[^"]*)" I expect (?:the nodes "(?[^"]*)"|no nodes) to be returned$/ + */ + public function iExecuteTheFindAncestorNodeAggregateIdsQueryIExpectTheFollowingNodes(string $entryNodeIdSerialized, string $expectedNodeIdsSerialized = ''): void + { + $entryNodeAggregateId = NodeAggregateId::fromString($entryNodeIdSerialized); + $expectedNodeIds = array_filter(explode(',', $expectedNodeIdsSerialized)); + $contentGraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName); + $actualNodeIds = $contentGraph->findAncestorNodeAggregateIds($entryNodeAggregateId)->toStringArray(); + Assert::assertSame($expectedNodeIds, $actualNodeIds, 'findAncestorNodeAggregateIds returned an unexpected result'); + } + /** * @When /^I execute the findClosestNode query for entry node aggregate id "(?[^"]*)"(?: and filter '(?[^']*)')? I expect (?:the node "(?[^"]*)"|no node) to be returned?$/ */ From 9801101d1629cea1e8aa778ef8da3e8f2715d939 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:40:42 +0200 Subject: [PATCH 07/15] TASK: Add behat test for multiple parent node aggregates --- .../NodeTraversal/AncestorNodes.feature | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature index 3bfeb899b64..2786d538c00 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature @@ -66,6 +66,7 @@ Feature: Find and count nodes using the findAncestorNodes, countAncestorNodes an | a2a2 | a2a2 | Neos.ContentRepository.Testing:Page | a2a | {} | {} | | a2a2a | a2a2a | Neos.ContentRepository.Testing:Page | a2a2 | {} | {} | | a2a2b | a2a2b | Neos.ContentRepository.Testing:Page | a2a2 | {} | {} | + | a2a2c | a2a2c | Neos.ContentRepository.Testing:Page | a2a2 | {} | {} | | a2b | a2b | Neos.ContentRepository.Testing:Page | a2 | {} | {} | | a2b1 | a2b1 | Neos.ContentRepository.Testing:Page | a2b | {} | {} | | b | b | Neos.ContentRepository.Testing:Page | home | {} | {} | @@ -77,7 +78,13 @@ Feature: Find and count nodes using the findAncestorNodes, countAncestorNodes an | Key | Value | | nodeAggregateId | "a2b" | | nodeVariantSelectionStrategy | "allVariants" | - + And the command MoveNodeAggregate is executed with payload: + | Key | Value | + | workspaceName | "live" | + | dimensionSpacePoint | {"language": "ch"} | + | relationDistributionStrategy | "scatter" | + | nodeAggregateId | "a2a2c" | + | newParentNodeAggregateId | "b" | Scenario: Subgraph queries # findAncestorNodes queries without results @@ -91,6 +98,11 @@ Feature: Find and count nodes using the findAncestorNodes, countAncestorNodes an When I execute the findAncestorNodes query for entry node aggregate id "a2a2b" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned and the total count to be 6 When I execute the findAncestorNodes query for entry node aggregate id "a2a2b" and filter '{"nodeTypes": "Neos.ContentRepository.Testing:Page"}' I expect the nodes "a2a2,a2,a" to be returned and the total count to be 3 + # a2a2c lives in dimension space ch beneath b + When I execute the findAncestorNodes query for entry node aggregate id "a2a2c" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned + And I am in dimension space point {"language":"ch"} + When I execute the findAncestorNodes query for entry node aggregate id "a2a2c" I expect the nodes "b,home,lady-eleonode-rootford" to be returned + Scenario: Contentgraph queries # findAncestorNodes queries without results @@ -101,6 +113,7 @@ Feature: Find and count nodes using the findAncestorNodes, countAncestorNodes an When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2a" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned # a2b is disabled When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2b1" I expect the nodes "a2b,a2,a,home,lady-eleonode-rootford" to be returned + # a2a2c lives in dimension space ch beneath b + When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2c" I expect the nodes "a2a2,b,a2a,home,a2,lady-eleonode-rootford,a" to be returned - # findAncestorNodes queries with results When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2b" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned From 16c0b309cb0d411fb4c59b156150a4503b7c4d85 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:14:55 +0200 Subject: [PATCH 08/15] FEATURE: Optimise `findAncestorNodeAggregateIds` to use CTE and sort result --- .../src/Domain/Repository/ContentGraph.php | 62 ++++++++++++++++--- .../NodeTraversal/AncestorNodes.feature | 2 +- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 178844f3f63..1fb2faab9ee 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -191,15 +191,39 @@ public function findParentNodeAggregates( public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregateId): NodeAggregateIds { - $stack = iterator_to_array($this->findParentNodeAggregates($entryNodeAggregateId)); + $queryBuilderInitial = $this->createQueryBuilder() + ->select('n.nodeAggregateId, 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('ph.contentstreamid = :contentStreamId') + ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); + + $queryBuilderRecursive = $this->createQueryBuilder() + ->select('pn.nodeAggregateId, 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') + ->where('h.contentstreamid = :contentStreamId'); + + $queryBuilderCte = $this->createQueryBuilder() + ->select('pn.nodeAggregateId') + ->orderBy('pn.parentnodeanchor', 'DESC') + ->from('ancestry', 'pn') + ->setParameter('contentStreamId', $this->contentStreamId->value) + ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); + + $nodeAggregateIdRows = $this->fetchCteResults( + $queryBuilderInitial, + $queryBuilderRecursive, + $queryBuilderCte, + 'ancestry' + ); - $ancestorNodeAggregateIds = []; - while ($stack !== []) { - $nodeAggregate = array_shift($stack); - $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; - array_push($stack, ...iterator_to_array($this->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); - } - return NodeAggregateIds::fromArray($ancestorNodeAggregateIds); + return NodeAggregateIds::fromArray(array_map(fn(array $row) => NodeAggregateId::fromString($row['nodeAggregateId']), $nodeAggregateIdRows)); } public function findChildNodeAggregates( @@ -352,6 +376,28 @@ private function fetchRows(QueryBuilder $queryBuilder): array } } + /** + * @return array> + */ + private function fetchCteResults(QueryBuilder $queryBuilderInitial, QueryBuilder $queryBuilderRecursive, QueryBuilder $queryBuilderCte, string $cteTableName = 'cte'): array + { + $query = <<getSQL()} + UNION + {$queryBuilderRecursive->getSQL()} + ) + {$queryBuilderCte->getSQL()} + SQL; + $parameters = array_merge($queryBuilderInitial->getParameters(), $queryBuilderRecursive->getParameters(), $queryBuilderCte->getParameters()); + $parameterTypes = array_merge($queryBuilderInitial->getParameterTypes(), $queryBuilderRecursive->getParameterTypes(), $queryBuilderCte->getParameterTypes()); + try { + return $this->dbal->fetchAllAssociative($query, $parameters, $parameterTypes); + } catch (DBALException $e) { + throw new \RuntimeException(sprintf('Failed to fetch CTE result: %s', $e->getMessage()), 1678358108, $e); + } + } + public function getContentStreamId(): ContentStreamId { return $this->contentStreamId; diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature index 2786d538c00..625cbf1237a 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature @@ -114,6 +114,6 @@ Feature: Find and count nodes using the findAncestorNodes, countAncestorNodes an # a2b is disabled When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2b1" I expect the nodes "a2b,a2,a,home,lady-eleonode-rootford" to be returned # a2a2c lives in dimension space ch beneath b - When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2c" I expect the nodes "a2a2,b,a2a,home,a2,lady-eleonode-rootford,a" to be returned + When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2c" I expect the nodes "a2a2,a2a,a2,b,a,home,lady-eleonode-rootford" to be returned When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2b" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned From 3cdf712e998c79ccfefd0dbd85200f04aaa9425b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:19:12 +0200 Subject: [PATCH 09/15] WIP position ordering for sibling in `findAncestorNodeAggregateIds` --- .../src/Domain/Repository/ContentGraph.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 1fb2faab9ee..8a184f6ee5f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -192,7 +192,7 @@ public function findParentNodeAggregates( public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregateId): NodeAggregateIds { $queryBuilderInitial = $this->createQueryBuilder() - ->select('n.nodeAggregateId, ph.parentnodeanchor') + ->select('n.nodeAggregateId, ph.parentnodeanchor, ph.position') ->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') @@ -203,7 +203,7 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); $queryBuilderRecursive = $this->createQueryBuilder() - ->select('pn.nodeAggregateId, h.parentnodeanchor') + ->select('pn.nodeAggregateId, h.parentnodeanchor, h.position') ->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') @@ -212,6 +212,7 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate $queryBuilderCte = $this->createQueryBuilder() ->select('pn.nodeAggregateId') ->orderBy('pn.parentnodeanchor', 'DESC') + ->addOrderBy('pn.position', 'DESC') ->from('ancestry', 'pn') ->setParameter('contentStreamId', $this->contentStreamId->value) ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); From 965cc343b5a88100477f2b400bc16de4ce675f74 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:36:59 +0200 Subject: [PATCH 10/15] TASK: Simplify code in `GraphProjectorCatchUpHookForCacheFlushing` --- .../GraphProjectorCatchUpHookForCacheFlushing.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php index ec79ed487ce..c5df6e95241 100644 --- a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php +++ b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php @@ -16,7 +16,6 @@ use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\EventInterface; -use Neos\ContentRepository\Core\Feature\Common\EmbedsContentStreamId; use Neos\ContentRepository\Core\Feature\Common\EmbedsNodeAggregateId; use Neos\ContentRepository\Core\Feature\Common\EmbedsWorkspaceName; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; @@ -38,7 +37,6 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Projection\CatchUpHookInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; -use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\Model\EventEnvelope; @@ -166,7 +164,6 @@ public function onBeforeEvent(EventInterface $eventInstance, EventEnvelope $even if ($nodeAggregate) { $this->scheduleCacheFlushJobForNodeAggregate( $this->contentRepository, - $eventInstance->workspaceName, $nodeAggregate, $contentGraph->findAncestorNodeAggregateIds($eventInstance->getNodeAggregateId()), ); @@ -195,7 +192,6 @@ public function onAfterEvent(EventInterface $eventInstance, EventEnvelope $event } elseif ( !($eventInstance instanceof NodeAggregateWasRemoved) && $eventInstance instanceof EmbedsNodeAggregateId - && $eventInstance instanceof EmbedsContentStreamId && $eventInstance instanceof EmbedsWorkspaceName ) { $contentGraph = $this->contentRepository->getContentGraph($eventInstance->getWorkspaceName()); @@ -206,7 +202,6 @@ public function onAfterEvent(EventInterface $eventInstance, EventEnvelope $event if ($nodeAggregate) { $this->scheduleCacheFlushJobForNodeAggregate( $this->contentRepository, - $eventInstance->getWorkspaceName(), $nodeAggregate, $contentGraph->findAncestorNodeAggregateIds($eventInstance->getNodeAggregateId()) ); @@ -216,14 +211,13 @@ public function onAfterEvent(EventInterface $eventInstance, EventEnvelope $event private function scheduleCacheFlushJobForNodeAggregate( ContentRepository $contentRepository, - WorkspaceName $workspaceName, NodeAggregate $nodeAggregate, NodeAggregateIds $ancestorNodeAggregateIds ): void { // we store this in an associative array deduplicate. - $this->flushNodeAggregateRequestsOnAfterCatchUp[$workspaceName->value . '__' . $nodeAggregate->nodeAggregateId->value] = FlushNodeAggregateRequest::create( + $this->flushNodeAggregateRequestsOnAfterCatchUp[$nodeAggregate->workspaceName->value . '__' . $nodeAggregate->nodeAggregateId->value] = FlushNodeAggregateRequest::create( $contentRepository->id, - $workspaceName, + $nodeAggregate->workspaceName, $nodeAggregate->nodeAggregateId, $nodeAggregate->nodeTypeName, $ancestorNodeAggregateIds From 947c3980da383c91808d7b230e0f3d83d3c7d2dd Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:25:18 +0200 Subject: [PATCH 11/15] TASK: Do not sort `findAncestorNodeAggregateIds` In the case of the content cache flusher we do not care about the order and ordering it by parentnodeanchor and position (for siblings) is slower and not even correct in all situations as the parentnodeanchor is just an autoincrement without meaning. --- .../src/Domain/Repository/ContentGraph.php | 6 ++---- .../Behavior/Features/NodeTraversal/AncestorNodes.feature | 8 ++++---- .../Projection/ContentGraph/ContentGraphInterface.php | 2 +- .../Behavior/Features/Bootstrap/NodeTraversalTrait.php | 4 ++-- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 8a184f6ee5f..3a06791d0a3 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -192,7 +192,7 @@ public function findParentNodeAggregates( public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregateId): NodeAggregateIds { $queryBuilderInitial = $this->createQueryBuilder() - ->select('n.nodeAggregateId, ph.parentnodeanchor, ph.position') + ->select('n.nodeAggregateId, 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') @@ -203,7 +203,7 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); $queryBuilderRecursive = $this->createQueryBuilder() - ->select('pn.nodeAggregateId, h.parentnodeanchor, h.position') + ->select('pn.nodeAggregateId, 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') @@ -211,8 +211,6 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate $queryBuilderCte = $this->createQueryBuilder() ->select('pn.nodeAggregateId') - ->orderBy('pn.parentnodeanchor', 'DESC') - ->addOrderBy('pn.position', 'DESC') ->from('ancestry', 'pn') ->setParameter('contentStreamId', $this->contentStreamId->value) ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature index 625cbf1237a..e796e54a761 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature @@ -110,10 +110,10 @@ Feature: Find and count nodes using the findAncestorNodes, countAncestorNodes an # findAncestorNodes queries with results # a2a2a is disabled - When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2a" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned + When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2a" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned in any order # a2b is disabled - When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2b1" I expect the nodes "a2b,a2,a,home,lady-eleonode-rootford" to be returned + When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2b1" I expect the nodes "a2b,a2,a,home,lady-eleonode-rootford" to be returned in any order # a2a2c lives in dimension space ch beneath b - When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2c" I expect the nodes "a2a2,a2a,a2,b,a,home,lady-eleonode-rootford" to be returned + When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2c" I expect the nodes "a2a2,a2a,a2,b,a,home,lady-eleonode-rootford" to be returned in any order - When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2b" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned + When I execute the findAncestorNodeAggregateIds query for entry node aggregate id "a2a2b" I expect the nodes "a2a2,a2a,a2,a,home,lady-eleonode-rootford" to be returned in any order diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 79b35b0bddf..95bbd94e788 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -110,7 +110,7 @@ public function findParentNodeAggregates( ): NodeAggregates; /** - * @internal + * @internal the returned order of node aggregate ids is undefined and not to be relied upon */ public function findAncestorNodeAggregateIds( NodeAggregateId $entryNodeAggregateId diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php index 4a0a25aad9b..52e90318601 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php @@ -294,7 +294,7 @@ public function iExecuteTheFindAncestorNodesQueryIExpectTheFollowingNodes(string } /** - * @When /^I execute the findAncestorNodeAggregateIds query for entry node aggregate id "(?[^"]*)" I expect (?:the nodes "(?[^"]*)"|no nodes) to be returned$/ + * @When /^I execute the findAncestorNodeAggregateIds query for entry node aggregate id "(?[^"]*)" I expect (?:the nodes "(?[^"]*)" to be returned in any order|no nodes to be returned)$/ */ public function iExecuteTheFindAncestorNodeAggregateIdsQueryIExpectTheFollowingNodes(string $entryNodeIdSerialized, string $expectedNodeIdsSerialized = ''): void { @@ -302,7 +302,7 @@ public function iExecuteTheFindAncestorNodeAggregateIdsQueryIExpectTheFollowingN $expectedNodeIds = array_filter(explode(',', $expectedNodeIdsSerialized)); $contentGraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName); $actualNodeIds = $contentGraph->findAncestorNodeAggregateIds($entryNodeAggregateId)->toStringArray(); - Assert::assertSame($expectedNodeIds, $actualNodeIds, 'findAncestorNodeAggregateIds returned an unexpected result'); + Assert::assertEqualsCanonicalizing($expectedNodeIds, $actualNodeIds, 'findAncestorNodeAggregateIds returned an unexpected result'); } /** From 5c1f1579f921155ad8c91536d3d7b218c893b97b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 27 Sep 2024 18:05:02 +0200 Subject: [PATCH 12/15] BUGFIX: Cannot replay "The source workspace missing does not exist" --- .../GraphProjectorCatchUpHookForCacheFlushing.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php index c5df6e95241..4abd1197744 100644 --- a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php +++ b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php @@ -37,6 +37,7 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Projection\CatchUpHookInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\Model\EventEnvelope; @@ -157,7 +158,11 @@ public function onBeforeEvent(EventInterface $eventInstance, EventEnvelope $even // cleared, leading to presumably duplicate nodes in the UI. || $eventInstance instanceof NodeAggregateWasMoved ) { - $contentGraph = $this->contentRepository->getContentGraph($eventInstance->workspaceName); + try { + $contentGraph = $this->contentRepository->getContentGraph($eventInstance->workspaceName); + } catch (WorkspaceDoesNotExist) { + return; + } $nodeAggregate = $contentGraph->findNodeAggregateById( $eventInstance->getNodeAggregateId() ); @@ -194,7 +199,11 @@ public function onAfterEvent(EventInterface $eventInstance, EventEnvelope $event && $eventInstance instanceof EmbedsNodeAggregateId && $eventInstance instanceof EmbedsWorkspaceName ) { - $contentGraph = $this->contentRepository->getContentGraph($eventInstance->getWorkspaceName()); + try { + $contentGraph = $this->contentRepository->getContentGraph($eventInstance->getWorkspaceName()); + } catch (WorkspaceDoesNotExist) { + return; + } $nodeAggregate = $contentGraph->findNodeAggregateById( $eventInstance->getNodeAggregateId() ); From 66c5513723c5d0d4c850a16deb0679ff24413262 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 30 Sep 2024 13:18:38 +0200 Subject: [PATCH 13/15] TASK: Remove obsolete join --- .../src/Domain/Repository/ContentGraph.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 3a06791d0a3..318d2bb6d6f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -192,14 +192,11 @@ public function findParentNodeAggregates( public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregateId): NodeAggregateIds { $queryBuilderInitial = $this->createQueryBuilder() - ->select('n.nodeAggregateId, ph.parentnodeanchor') + ->select('n.nodeAggregateId, ch.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('ph.contentstreamid = :contentStreamId') ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); $queryBuilderRecursive = $this->createQueryBuilder() From 2dbd3a04a89694ce2efaf50f2fcca195740ea953 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 27 Oct 2024 19:37:40 +0100 Subject: [PATCH 14/15] TASK: Task rename alias in ancestor queries `cn` should actually be named `ch` as it's joined as the child hierarchy relation --- .../src/Domain/Repository/ContentGraph.php | 11 ++++++----- .../src/Domain/Repository/ContentSubgraph.php | 5 +++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 88569fd0773..ca019e17fa7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -55,6 +55,7 @@ * - cn -> child node * - h -> the hierarchy edge connecting parent and child * - ph -> the hierarchy edge incoming to the parent (sometimes relevant) + * - ch -> the hierarchy edge of the child (sometimes relevant) * - dsp -> dimension space point, resolves hashes to full dimension coordinates * - cdsp -> child dimension space point, same as dsp for child queries * - pdsp -> parent dimension space point, same as dsp for parent queries @@ -199,11 +200,11 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); $queryBuilderRecursive = $this->createQueryBuilder() - ->select('pn.nodeAggregateId, 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') - ->where('h.contentstreamid = :contentStreamId'); + ->select('pn.nodeAggregateId, ph.parentnodeanchor') + ->from('ancestry', 'ch') + ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = ch.parentnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->where('ph.contentstreamid = :contentStreamId'); $queryBuilderCte = $this->createQueryBuilder() ->select('pn.nodeAggregateId') diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 810a095069f..b273c36a402 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -74,6 +74,7 @@ * - cn -> child node * - h -> the hierarchy edge connecting parent and child * - ph -> the hierarchy edge incoming to the parent (sometimes relevant) + * - ch -> the hierarchy edge of the child (sometimes relevant) * * - if more than one node (source-destination) * - sn -> source node @@ -570,8 +571,8 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId $queryBuilderRecursive = $this->createQueryBuilder() ->select('pn.*, h.subtreetags, h.parentnodeanchor') - ->from('ancestry', 'cn') - ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') + ->from('ancestry', 'ch') + ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = ch.parentnodeanchor') ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); From d75afc0dfb653720c752de0e63136a24e71be24c Mon Sep 17 00:00:00 2001 From: Marc Henry Schultz <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:31:03 +0100 Subject: [PATCH 15/15] TASK: Optimise `findAncestorNodeAggregateIds` The query could be optimized a bit more (at least for deeper trees), if we join the node table only after we built the hierarchy. Co-authored-by: Denny Lubitz --- .../src/Domain/Repository/ContentGraph.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index ca019e17fa7..7220c896a0b 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -192,23 +192,22 @@ public function findParentNodeAggregates( public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregateId): NodeAggregateIds { $queryBuilderInitial = $this->createQueryBuilder() - ->select('n.nodeAggregateId, ch.parentnodeanchor') - ->from($this->nodeQueryBuilder->tableNames->node(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->select('ch.parentnodeanchor') + ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); $queryBuilderRecursive = $this->createQueryBuilder() - ->select('pn.nodeAggregateId, ph.parentnodeanchor') + ->select('ph.parentnodeanchor') ->from('ancestry', 'ch') - ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = ch.parentnodeanchor') - ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = ch.parentnodeanchor') ->where('ph.contentstreamid = :contentStreamId'); $queryBuilderCte = $this->createQueryBuilder() - ->select('pn.nodeAggregateId') - ->from('ancestry', 'pn') + ->select('n.nodeAggregateId') + ->from('ancestry', 'a') + ->innerJoin('a', $this->nodeQueryBuilder->tableNames->node(), 'n', 'n.relationanchorpoint = a.parentnodeanchor') ->setParameter('contentStreamId', $this->contentStreamId->value) ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value);