Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/9.0' into feature/4746-rework-ca…
Browse files Browse the repository at this point in the history
…tchup-mechanism-3
  • Loading branch information
mhsdesign committed Dec 2, 2024
2 parents 9286afc + 1fa7ae3 commit d13c153
Show file tree
Hide file tree
Showing 26 changed files with 211 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,13 @@ public function determineHierarchyRelationPosition(
throw new \RuntimeException(sprintf('Failed to load succeeding sibling relations for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamId->value, $succeedingSiblingAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716474854, $e);
}

if (!$succeedingSiblingRelation) {
throw new \RuntimeException(
sprintf('Could not fetch succeeding sibling relation for anchor point: %s with dimensionSpacePointHash : %s', $succeedingSiblingAnchorPoint->value, $dimensionSpacePoint->hash),
1696405259
);
}

$succeedingSiblingPosition = (int)$succeedingSiblingRelation['position'];
$parentAnchorPoint = NodeRelationAnchorPoint::fromInteger($succeedingSiblingRelation['parentnodeanchor']);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Feature: Workspace discarding - complex chained functionality
| workspaceName | "user-ws" |
| nodesToDiscard | [{"workspaceName": "user-ws", "dimensionSpacePoint": {"language": "en"}, "nodeAggregateId": "sir-david-nodenborough"}, {"workspaceName": "user-ws", "dimensionSpacePoint": {"language": "en"}, "nodeAggregateId": "sir-david-nodenborough"}] |
| newContentStreamId | "user-cs-id-rebased" |
Then the last command should have thrown the WorkspaceRebaseFailed exception with:
Then the last command should have thrown the PartialWorkspaceRebaseFailed exception with:
| SequenceNumber | Event | Exception |
| 11 | NodeGeneralizationVariantWasCreated | NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ Feature: Workspace publication - complex chained functionality
| originDimensionSpacePoint | {"language": "de"} |
| propertyValues | {"text": "Modified text"} |

Then workspace user-ws has status OUTDATED

When the command PublishIndividualNodesFromWorkspace is executed with payload and exceptions are caught:
| Key | Value |
| workspaceName | "user-ws" |
Expand All @@ -101,12 +103,14 @@ Feature: Workspace publication - complex chained functionality
| sourceOrigin | {"language": "de"} |
| targetOrigin | {"language": "en"} |

Then workspace user-ws has status UP_TO_DATE

When the command PublishIndividualNodesFromWorkspace is executed with payload and exceptions are caught:
| Key | Value |
| workspaceName | "user-ws" |
| nodesToPublish | [{"workspaceName": "user-ws", "dimensionSpacePoint": {"language": "en"}, "nodeAggregateId": "nody-mc-nodeface"}] |
| newContentStreamId | "user-cs-id-rebased" |
Then the last command should have thrown the WorkspaceRebaseFailed exception with:
Then the last command should have thrown the PartialWorkspaceRebaseFailed exception with:
| SequenceNumber | Event | Exception |
| 13 | NodeGeneralizationVariantWasCreated | NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint |

Expand All @@ -116,3 +120,32 @@ Feature: Workspace publication - complex chained functionality
| newContentStreamId | "user-cs-id-yet-again-rebased" |
When I am in workspace "user-ws" and dimension space point {"language": "de"}
Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-id-yet-again-rebased;nody-mc-nodeface;{"language": "de"}

Scenario: Publish a deletion and try to keep a move node from its descendants
see issue: https://github.com/neos/neos-development-collection/issues/5364

When the command MoveNodeAggregate is executed with payload:
| Key | Value |
| workspaceName | "user-ws" |
| nodeAggregateId | "nody-mc-nodeface" |
| dimensionSpacePoint | {"language": "de"} |
| newParentNodeAggregateId | "sir-nodebelig" |
| relationDistributionStrategy | "gatherAll" |

When the command RemoveNodeAggregate is executed with payload:
| Key | Value |
| workspaceName | "user-ws" |
| nodeAggregateId | "sir-david-nodenborough" |
| coveredDimensionSpacePoint | {"language": "de"} |
| nodeVariantSelectionStrategy | "allVariants" |

Then workspace user-ws has status UP_TO_DATE

When the command PublishIndividualNodesFromWorkspace is executed with payload and exceptions are caught:
| Key | Value |
| workspaceName | "user-ws" |
| nodesToPublish | [{"dimensionSpacePoint": {"language": "de"}, "nodeAggregateId": "sir-david-nodenborough"}] |
| newContentStreamId | "user-cs-id-rebased" |
Then the last command should have thrown the PartialWorkspaceRebaseFailed exception with:
| SequenceNumber | Event | Exception |
| 11 | NodeAggregateWasMoved | NodeAggregateCurrentlyDoesNotExist |
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ private function __construct(
* @param WorkspaceName $workspaceName The workspace in which the move operation is to be performed
* @param DimensionSpacePoint $dimensionSpacePoint This is one of the *covered* dimension space points of the node aggregate and not necessarily one of the occupied ones. This allows us to move virtual specializations only when using the scatter strategy
* @param NodeAggregateId $nodeAggregateId The id of the node aggregate to move
* @param RelationDistributionStrategy $relationDistributionStrategy The relation distribution strategy to be used ({@see RelationDistributionStrategy})
* @param RelationDistributionStrategy $relationDistributionStrategy The relation distribution strategy to be used ({@see RelationDistributionStrategy}).
* @param NodeAggregateId|null $newParentNodeAggregateId The id of the new parent node aggregate. If given, it enforces that all nodes in the given aggregate are moved into nodes of the parent aggregate, even if the given siblings belong to other parents. In latter case, those siblings are ignored
* @param NodeAggregateId|null $newPrecedingSiblingNodeAggregateId The id of the new preceding sibling node aggregate. If given and no successor found, it is attempted to insert the moved nodes right after nodes of this aggregate. In dimension space points this aggregate does not cover, other siblings, in order of proximity, are tried to be used instead
* @param NodeAggregateId|null $newSucceedingSiblingNodeAggregateId The id of the new succeeding sibling node aggregate. If given, it is attempted to insert the moved nodes right before nodes of this aggregate. In dimension space points this aggregate does not cover, the preceding sibling is tried to be used instead
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Command\RebaseWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\PartialWorkspaceRebaseFailed;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed;
use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamAlreadyExists;
use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet;
Expand Down Expand Up @@ -507,8 +508,14 @@ static function ($handle) use ($commandSimulator, $matchingCommands, $remainingC
yield $this->reopenContentStreamWithoutConstraintChecks(
$workspace->currentContentStreamId
);

throw WorkspaceRebaseFailed::duringPublish($commandSimulator->getConflictingEvents());
match ($workspace->status) {
// If the workspace is up-to-date it must be a problem regarding that the order of events cannot be changed
WorkspaceStatus::UP_TO_DATE =>
throw PartialWorkspaceRebaseFailed::duringPartialPublish($commandSimulator->getConflictingEvents()),
// If the workspace is outdated we cannot know for sure but suspect that the conflict arose due to changes in the base workspace.
WorkspaceStatus::OUTDATED =>
throw WorkspaceRebaseFailed::duringPublish($commandSimulator->getConflictingEvents())
};
}

// this could empty and a no-op for the rare case when a command returns empty events e.g. the node was already tagged with this subtree tag
Expand Down Expand Up @@ -608,7 +615,6 @@ private function handleDiscardIndividualNodesFromWorkspace(
// quick path everything was discarded
yield from $this->discardWorkspace(
$workspace,
$workspaceContentStreamVersion,
$baseWorkspace,
$baseWorkspaceContentStreamVersion,
$command->newContentStreamId
Expand All @@ -630,7 +636,14 @@ static function ($handle) use ($commandsToKeep): void {
yield $this->reopenContentStreamWithoutConstraintChecks(
$workspace->currentContentStreamId
);
throw WorkspaceRebaseFailed::duringDiscard($commandSimulator->getConflictingEvents());
match ($workspace->status) {
// If the workspace is up-to-date it must be a problem regarding that the order of events cannot be changed
WorkspaceStatus::UP_TO_DATE =>
throw PartialWorkspaceRebaseFailed::duringPartialDiscard($commandSimulator->getConflictingEvents()),
// If the workspace is outdated we cannot know for sure but suspect that the conflict arose due to changes in the base workspace.
WorkspaceStatus::OUTDATED =>
throw WorkspaceRebaseFailed::duringDiscard($commandSimulator->getConflictingEvents())
};
}

yield from $this->forkNewContentStreamAndApplyEvents(
Expand Down Expand Up @@ -675,12 +688,11 @@ private function handleDiscardWorkspace(
return;
}

$workspaceContentStreamVersion = $this->requireOpenContentStreamAndVersion($workspace, $commandHandlingDependencies);
$this->requireContentStreamToNotBeClosed($workspace->currentContentStreamId, $commandHandlingDependencies);
$baseWorkspaceContentStreamVersion = $this->requireOpenContentStreamAndVersion($baseWorkspace, $commandHandlingDependencies);

yield from $this->discardWorkspace(
$workspace,
$workspaceContentStreamVersion,
$baseWorkspace,
$baseWorkspaceContentStreamVersion,
$command->newContentStreamId
Expand All @@ -692,7 +704,6 @@ private function handleDiscardWorkspace(
*/
private function discardWorkspace(
Workspace $workspace,
Version $workspaceContentStreamVersion,
Workspace $baseWorkspace,
Version $baseWorkspaceContentStreamVersion,
ContentStreamId $newContentStream
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

/*
* This file is part of the Neos.ContentRepository.Core package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception;

use Neos\ContentRepository\Core\Feature\WorkspaceRebase\ConflictingEvents;

/**
* Thrown if the partial publish/discard cannot work because the events cannot be reordered as filtered.
*
* This can happen for cases like attempting to publish a removal first and wanting as remaining change
* a node move out of the removed descendants or publishing a node variant creation before the node is created.
*
* We cannot reliably detect these cases in advance but in case the workspace is up-to-date its most likely such
* an ordering conflict.
*
* To solve the problem the partial operation should be retried with a different filter _or_ a full publish/discard is required.
*
* If the workspace is outdated we cannot know for sure but suspect first that the conflict arose due to changes
* in the base workspace, thus we throw {@see WorkspaceRebaseFailed} instead.
* A forced rebase then might not solve the problem if It's because the order of events cannot be changed.
* But attempting a second partial publish/discard (with up-to-date workspace) this exception will be thrown and can be reacted upon.
*
* @see WorkspaceRebaseFailed which is thrown instead if the workspace is also outdated
* @api this exception contains information which events cannot be reordered
*/
final class PartialWorkspaceRebaseFailed extends \RuntimeException
{
private function __construct(
public readonly ConflictingEvents $conflictingEvents,
string $message,
int $code,
?\Throwable $previous,
) {
parent::__construct($message, $code, $previous);
}

public static function duringPartialPublish(ConflictingEvents $conflictingEvents): self
{
return new self(
$conflictingEvents,
sprintf('Publication failed, events cannot be reordered as filtered: %s', self::renderMessage($conflictingEvents)),
1729974980,
$conflictingEvents->first()?->getException()
);
}

public static function duringPartialDiscard(ConflictingEvents $conflictingEvents): self
{
return new self(
$conflictingEvents,
sprintf('Discard failed, events cannot be reordered as filtered: %s', self::renderMessage($conflictingEvents)),
1729974982,
$conflictingEvents->first()?->getException()
);
}

private static function renderMessage(ConflictingEvents $conflictingEvents): string
{
$firstConflict = $conflictingEvents->first();
return sprintf('"%s" and %d further ordering conflicts', $firstConflict?->getException()->getMessage(), count($conflictingEvents) - 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
/**
* @api this exception contains information about what exactly went wrong during rebase
*/
final class WorkspaceRebaseFailed extends \Exception
final class WorkspaceRebaseFailed extends \RuntimeException
{
private function __construct(
public readonly ConflictingEvents $conflictingEvents,
Expand Down
19 changes: 0 additions & 19 deletions Neos.ContentRepository.Core/Classes/NodeType/NodeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,25 +291,6 @@ public function getDeclaredSuperTypes(): array
});
}

/**
* Returns whether this node type (or any parent type) is an *aggregate*.
*
* The most prominent *aggregate* is a Document and everything which inherits from it, like a Page.
*
* If a node type is marked as aggregate, it means that:
*
* - the node type can "live on its own", i.e. can be part of an external URL
* - when moving this node, all node variants are also moved (across all dimensions)
* - Recursive copying only happens *inside* this aggregate, and stops at nested aggregates.
*
* @return boolean true if the node type is an aggregate
* @api
*/
public function isAggregate(): bool
{
return $this->getConfiguration('aggregate') === true;
}

/**
* If this node type or any of the direct or indirect super types
* has the given name.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ protected function prepareNodeTypeManager(array $nodeTypesFixtureData)
],
'Neos.ContentRepository.Testing:Document' => [
'abstract' => true,
'aggregate' => true
],
'Neos.ContentRepository.Testing:Page' => [
'superTypes' => ['Neos.ContentRepository.Testing:Document' => true],
Expand Down Expand Up @@ -273,23 +272,6 @@ public function getNodeTypeAllowsToRetrieveFinalNodeTypes()
self::assertTrue($this->nodeTypeManager->getNodeType('Neos.ContentRepository.Testing:MyFinalType')->isFinal());
}

/**
* @test
*/
public function aggregateNodeTypeFlagIsFalseByDefault()
{
self::assertFalse($this->nodeTypeManager->getNodeType('Neos.ContentRepository.Testing:Text')->isAggregate());
}

/**
* @test
*/
public function aggregateNodeTypeFlagIsInherited()
{
self::assertTrue($this->nodeTypeManager->getNodeType('Neos.ContentRepository.Testing:Document')->isAggregate());
self::assertTrue($this->nodeTypeManager->getNodeType('Neos.ContentRepository.Testing:Page')->isAggregate());
}

/**
* @test
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,9 @@ public function iExpectTheFollowingJsonL(PyStringNode $string): void
$eventsWithoutRandomIds = [];

foreach ($exportedEvents as $exportedEvent) {
// we have to remove the event id and initiatingTimestamp to make the events diff able
// we have to remove the event ids to make the events diff-able
$eventsWithoutRandomIds[] = $exportedEvent
->withIdentifier('random-event-uuid')
->processMetadata(function (array $metadata) {
$metadata[InitiatingEventMetadata::INITIATING_TIMESTAMP] = 'random-time';
return $metadata;
});
->withIdentifier('random-event-uuid');
}

Assert::assertSame($string->getRaw(), ExportedEvents::fromIterable($eventsWithoutRandomIds)->toJsonl());
Expand Down
Loading

0 comments on commit d13c153

Please sign in to comment.