From 698332245430d0a859949d5902c3a74c65987906 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:00:02 +0100 Subject: [PATCH] BUGFIX: flow neosBetaMigration:reorderNodeAggregateWasRemoved reparation for upgrading to beta 15 see also https://github.com/neos/neos-development-collection/issues/5364 --- .../NeosBetaMigrationCommandController.php | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/Classes/Command/NeosBetaMigrationCommandController.php b/Classes/Command/NeosBetaMigrationCommandController.php index aa2ff5c..8f80537 100644 --- a/Classes/Command/NeosBetaMigrationCommandController.php +++ b/Classes/Command/NeosBetaMigrationCommandController.php @@ -4,13 +4,25 @@ namespace Neos\ContentRepositoryRegistry\Command; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; +use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; +use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; +use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; +use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Projection\CatchUpOptions; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\ContentRepositoryRegistry\Factory\EventStore\DoctrineEventStoreFactory; +use Neos\EventStore\Model\Event\EventType; +use Neos\EventStore\Model\Event\EventTypes; use Neos\EventStore\Model\Event\SequenceNumber; +use Neos\EventStore\Model\EventEnvelope; +use Neos\EventStore\Model\Events; +use Neos\EventStore\Model\EventStream\EventStreamFilter; +use Neos\EventStore\Model\EventStream\ExpectedVersion; use Neos\Flow\Annotations as Flow; use Neos\Flow\Cli\CommandController; use Symfony\Component\Console\Helper\ProgressBar; @@ -23,6 +35,44 @@ class NeosBetaMigrationCommandController extends CommandController #[Flow\Inject()] protected Connection $connection; + public function reorderNodeAggregateWasRemovedCommand(string $contentRepository = 'default', string $workspaceName = 'live'): void + { + $contentRepositoryId = ContentRepositoryId::fromString($contentRepository); + $this->backup($contentRepositoryId); + + $workspace = $this->contentRepositoryRegistry->get($contentRepositoryId)->findWorkspaceByName(WorkspaceName::fromString($workspaceName)); + if (!$workspace) { + $this->outputLine('Workspace not found'); + $this->quit(1); + } + + $streamName = ContentStreamEventStreamName::fromContentStreamId($workspace->currentContentStreamId)->getEventStreamName(); + + $internals = $this->getInternals($contentRepositoryId); + + // get all NodeAggregateWasRemoved from the content stream + $eventsToReorder = iterator_to_array($internals->eventStore->load($streamName, EventStreamFilter::create(EventTypes::create(EventType::fromString('NodeAggregateWasRemoved')))), false); + + // remove all the NodeAggregateWasRemoved events at their sequenceNumbers + $eventTableName = DoctrineEventStoreFactory::databaseTableName($contentRepositoryId); + $this->connection->beginTransaction(); + $this->connection->executeStatement( + 'DELETE FROM ' . $eventTableName . ' WHERE sequencenumber IN (:sequenceNumbers)', + [ + 'sequenceNumbers' => array_map(fn (EventEnvelope $eventEnvelope) => $eventEnvelope->sequenceNumber->value, $eventsToReorder) + ], + [ + 'sequenceNumbers' => ArrayParameterType::STRING + ] + ); + $this->connection->commit(); + + // reapply the NodeAggregateWasRemoved events at the end + $internals->eventStore->commit($streamName, Events::fromArray(array_map(fn (EventEnvelope $eventEnvelope) => $eventEnvelope->event, $eventsToReorder)), ExpectedVersion::ANY()); + + $this->outputLine('Reordered %d removals. Please replay and rebase your other workspaces.', [count($eventsToReorder)]); + } + public function fixReplayCommand(string $contentRepository = 'default', bool $resetProjection = true): void { $contentRepositoryId = ContentRepositoryId::fromString($contentRepository); @@ -149,4 +199,21 @@ private function copyEventTable(string $backupEventTableName, ContentRepositoryI FROM ' . $eventTableName ); } + + private function getInternals(ContentRepositoryId $contentRepositoryId): ContentRepositoryServiceFactoryDependencies + { + // NOT API!!! + $accessor = new class implements ContentRepositoryServiceFactoryInterface { + public ContentRepositoryServiceFactoryDependencies|null $dependencies; + public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface + { + $this->dependencies = $serviceFactoryDependencies; + return new class implements ContentRepositoryServiceInterface + { + }; + } + }; + $this->contentRepositoryRegistry->buildService($contentRepositoryId, $accessor); + return $accessor->dependencies; + } }