From 630adf18f555fd57c3bb751ba3db55634637711a Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 2 Oct 2024 23:39:13 +0200 Subject: [PATCH 01/11] TASK: Test mark dependents of live outdated after direct change https://github.com/neos/neos-development-collection/issues/4508 --- .../Workspaces/WorkspaceState.feature | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/WorkspaceState.feature diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/WorkspaceState.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/WorkspaceState.feature new file mode 100644 index 00000000000..74e3fcd502a --- /dev/null +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/WorkspaceState.feature @@ -0,0 +1,109 @@ +@contentrepository @adapters=DoctrineDBAL +Feature: Workspace status + The workspace status signals if the workspace is UP_TO_DATE or OUTDATED + All depending workspaces are considered OUTDATED if changes are made or published into a workspace + + Background: + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:Content': + properties: + text: + type: string + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | newContentStreamId | "cs-identifier" | + And I am in workspace "live" and dimension space point {} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | + | nody-mc-nodeface | Neos.ContentRepository.Testing:Content | lady-eleonode-rootford | child | + + And the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "live" | + | nodeAggregateId | "nody-mc-nodeface" | + | originDimensionSpacePoint | {} | + | propertyValues | {"text": "Original"} | + + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-ws-one" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-one" | + + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "shared" | + | baseWorkspaceName | "live" | + | newContentStreamId | "shared-cs-identifier" | + + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-ws-two" | + | baseWorkspaceName | "shared" | + | newContentStreamId | "user-cs-two" | + + Scenario: Changes to the root workspace render dependents outdated + Then workspaces live,shared,user-ws-one,user-ws-two have status UP_TO_DATE + + And the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "live" | + | nodeAggregateId | "nody-mc-nodeface" | + | originDimensionSpacePoint | {} | + | propertyValues | {"text": "Revision"} | + + Then workspace live has status UP_TO_DATE + Then workspaces shared,user-ws-one have status OUTDATED + # the others users workspace is not outdated because it depends on shared + Then workspace user-ws-two has status UP_TO_DATE + + Scenario: Publishing to the root workspace render dependents outdated + Then workspaces live,shared,user-ws-one,user-ws-two have status UP_TO_DATE + + And the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-ws-one" | + | nodeAggregateId | "nody-mc-nodeface" | + | originDimensionSpacePoint | {} | + | propertyValues | {"text": "Revision"} | + + Then workspaces live,shared,user-ws-one,user-ws-two have status UP_TO_DATE + + And the command PublishWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-ws-one" | + + Then workspaces live,user-ws-one have status UP_TO_DATE + Then workspace shared has status OUTDATED + # the others users workspace is not outdated because it depends on shared + Then workspace user-ws-two has status UP_TO_DATE + + # + # Rebasing to get everything up to date + # + + When the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "shared" | + | rebasedContentStreamId | "shared-rebased" | + + Then workspaces live,shared,user-ws-one have status UP_TO_DATE + Then workspace user-ws-two has status OUTDATED + + When the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-ws-two" | + | rebasedContentStreamId | "user-ws-two-rebased" | + + Then workspaces live,shared,user-ws-one,user-ws-two have status UP_TO_DATE From adc52b783b30a16218f8eeb0279e037e53da5466 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 2 Oct 2024 23:41:30 +0200 Subject: [PATCH 02/11] TASK: Improve workspace status behat step and adjust tests to be more precise --- .../03-RebasingWithConflictingChanges.feature | 9 +++++++-- .../02-DiscardWorkspace.feature | 14 +++++++++++--- .../Features/Bootstrap/CRTestSuiteTrait.php | 13 +++++++++---- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/03-RebasingWithConflictingChanges.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/03-RebasingWithConflictingChanges.feature index d12182497d7..c90c66de6e0 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/03-RebasingWithConflictingChanges.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/03-RebasingWithConflictingChanges.feature @@ -45,7 +45,7 @@ Feature: Workspace rebasing - conflicting changes | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-identifier" | - Scenario: Conflicting changes lead to OUTDATED_CONFLICT which can be recovered from via forced rebase + Scenario: Conflicting changes lead to OUTDATED which can be recovered from via forced rebase When the command CreateWorkspace is executed with payload: | Key | Value | @@ -58,6 +58,8 @@ Feature: Workspace rebasing - conflicting changes | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-two" | + Then workspaces live,user-ws-one,user-ws-two have status UP_TO_DATE + When the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | @@ -87,10 +89,13 @@ Feature: Workspace rebasing - conflicting changes | originDimensionSpacePoint | {} | | propertyValues | {"text": "The other node"} | + Then workspaces live,user-ws-one,user-ws-two have status UP_TO_DATE + And the command PublishWorkspace is executed with payload: | Key | Value | | workspaceName | "user-ws-one" | + Then workspaces live,user-ws-one have status UP_TO_DATE Then workspace user-ws-two has status OUTDATED When the command RebaseWorkspace is executed with payload: @@ -99,5 +104,5 @@ Feature: Workspace rebasing - conflicting changes | rebasedContentStreamId | "user-cs-two-rebased" | | rebaseErrorHandlingStrategy | "force" | - Then workspace user-ws-two has status UP_TO_DATE + Then workspaces live,user-ws-one,user-ws-two have status UP_TO_DATE And I expect a node identified by user-cs-two-rebased;noderus-secundus;{} to exist in the content graph diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature index 2174937dad7..8c7b20cea41 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature @@ -98,7 +98,7 @@ Feature: Workspace discarding - basic functionality | Key | Value | | text | "Modified in live workspace" | - Scenario: Conflicting changes lead to OUTDATED which can be recovered from via discard + Scenario: Conflicting changes lead to exception on rebase which can be recovered from via discard When the command CreateWorkspace is executed with payload: | Key | Value | @@ -111,6 +111,8 @@ Feature: Workspace discarding - basic functionality | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-two" | + Then workspaces live,user-ws-one,user-ws-two have status UP_TO_DATE + When the command RemoveNodeAggregate is executed with payload: | Key | Value | | workspaceName | "user-ws-one" | @@ -129,14 +131,14 @@ Feature: Workspace discarding - basic functionality | Key | Value | | workspaceName | "user-ws-one" | + Then workspaces live,user-ws-one have status UP_TO_DATE Then workspace user-ws-two has status OUTDATED When the command RebaseWorkspace is executed with payload and exceptions are caught: | Key | Value | | workspaceName | "user-ws-two" | | rebasedContentStreamId | "user-cs-two-rebased" | - - Then workspace user-ws-two has status OUTDATED + Then the last command should have thrown an exception of type "WorkspaceRebaseFailed" When the command DiscardWorkspace is executed with payload: | Key | Value | @@ -145,3 +147,9 @@ Feature: Workspace discarding - basic functionality Then workspace user-ws-two has status OUTDATED + When the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-ws-two" | + | rebasedContentStreamId | "user-cs-two-rebased-final" | + + Then workspaces live,user-ws-one,user-ws-two have status UP_TO_DATE diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php index ab550c6bd35..430b66870d8 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -140,13 +140,18 @@ public function iExpectTheContentStreamToNotExist(string $rawContentStreamId): v } /** - * @Then /^workspace ([^"]*) has status ([^"]*)$/ + * @Then /^workspace(?:s)? ([^"]*) ha(?:s|ve) status ([^"]*)$/ */ - public function workspaceHasStatus(string $rawWorkspaceName, string $status): void + public function workspaceStatusMatchesExpected(string $rawWorkspaceNames, string $status): void { - $workspace = $this->currentContentRepository->findWorkspaceByName(WorkspaceName::fromString($rawWorkspaceName)); + $rawWorkspaceNames = explode(',', $rawWorkspaceNames); + Assert::assertNotEmpty($rawWorkspaceNames); - Assert::assertSame($status, $workspace?->status->value); + foreach ($rawWorkspaceNames as $rawWorkspaceName) { + $workspace = $this->currentContentRepository->findWorkspaceByName(WorkspaceName::fromString($rawWorkspaceName)); + Assert::assertNotNull($workspace, "Workspace $rawWorkspaceName does not exist."); + Assert::assertEquals($status, $workspace->status->value, "Workspace $rawWorkspaceName has unexpected status."); + } } /** From c22a4ac5d01bc93d04c62e3106c7be3c0ca1e602 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:43:19 +0200 Subject: [PATCH 03/11] BUGFIX: Calculate workspace state by comparing source cs version and known source version --- .../src/ContentRepositoryReadModelAdapter.php | 23 ++++++++++++++----- .../DoctrineDbalContentGraphProjection.php | 2 +- .../DoctrineDbalContentGraphSchemaBuilder.php | 3 ++- .../Projection/Feature/ContentStream.php | 3 ++- .../02-DiscardWorkspace.feature | 9 ++------ 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php index 56ad85a9d63..05bd545d25a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php @@ -55,11 +55,13 @@ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { $workspaceByNameStatement = <<tableNames->workspace()} + {$this->tableNames->workspace()} ws + JOIN {$this->tableNames->contentStream()} cs ON cs.id = ws.currentcontentstreamid + LEFT JOIN {$this->tableNames->contentStream()} scs ON cs.sourceContentStreamId = scs.id WHERE - name = :workspaceName + ws.name = :workspaceName LIMIT 1 SQL; try { @@ -79,9 +81,11 @@ public function findWorkspaces(): Workspaces { $workspacesStatement = <<tableNames->workspace()} + {$this->tableNames->workspace()} ws + JOIN {$this->tableNames->contentStream()} cs ON cs.id = ws.currentcontentstreamid + LEFT JOIN {$this->tableNames->contentStream()} scs ON cs.sourceContentStreamId = scs.id SQL; try { $rows = $this->dbal->fetchAllAssociative($workspacesStatement); @@ -136,11 +140,18 @@ public function findContentStreams(): ContentStreams */ private static function workspaceFromDatabaseRow(array $row): Workspace { + if ($row['sourceVersion'] === null) { + $status = WorkspaceStatus::UP_TO_DATE; + } elseif ($row['sourceVersion'] > $row['lastSourceVersion']) { + $status = WorkspaceStatus::OUTDATED; + } else { + $status = WorkspaceStatus::UP_TO_DATE; + } return new Workspace( WorkspaceName::fromString($row['name']), isset($row['baseWorkspaceName']) ? WorkspaceName::fromString($row['baseWorkspaceName']) : null, ContentStreamId::fromString($row['currentContentStreamId']), - WorkspaceStatus::from($row['status']), + $status, ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index ecd410b0a07..527ed421d96 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -306,7 +306,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void // NOTE: as reference edges are attached to Relation Anchor Points (and they are lazily copy-on-written), // we do not need to copy reference edges here (but we need to do it during copy on write). - $this->createContentStream($event->newContentStreamId, ContentStreamStatus::FORKED, $event->sourceContentStreamId); + $this->createContentStream($event->newContentStreamId, ContentStreamStatus::FORKED, $event->sourceContentStreamId, $event->versionOfSourceContentStream); } private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): void diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index e10b4f16c62..05dd58f0ae7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -129,7 +129,8 @@ private function createContentStreamTable(): Table DbalSchemaFactory::columnForContentStreamId('sourceContentStreamId')->setNotnull(false), // Should become a DB ENUM (unclear how to configure with DBAL) or int (latter needs adaption to code) (new Column('status', Type::getType(Types::BINARY)))->setLength(20)->setNotnull(true), - (new Column('removed', Type::getType(Types::BOOLEAN)))->setDefault(false)->setNotnull(false) + (new Column('removed', Type::getType(Types::BOOLEAN)))->setDefault(false)->setNotnull(false), + (new Column('sourceVersion', Type::getType(Types::INTEGER)))->setNotnull(false), ]); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php index dfe1f7f46d5..8ee20873714 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php @@ -15,12 +15,13 @@ */ trait ContentStream { - private function createContentStream(ContentStreamId $contentStreamId, ContentStreamStatus $status, ?ContentStreamId $sourceContentStreamId = null): void + private function createContentStream(ContentStreamId $contentStreamId, ContentStreamStatus $status, ?ContentStreamId $sourceContentStreamId = null, ?Version $sourceVersion = null): void { $this->dbal->insert($this->tableNames->contentStream(), [ 'id' => $contentStreamId->value, 'sourceContentStreamId' => $sourceContentStreamId?->value, 'version' => 0, + 'sourceVersion' => $sourceVersion?->value, 'status' => $status->value, ]); } diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature index 8c7b20cea41..ca3a0f6ed88 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature @@ -140,16 +140,11 @@ Feature: Workspace discarding - basic functionality | rebasedContentStreamId | "user-cs-two-rebased" | Then the last command should have thrown an exception of type "WorkspaceRebaseFailed" + Then workspace user-ws-two has status OUTDATED + When the command DiscardWorkspace is executed with payload: | Key | Value | | workspaceName | "user-ws-two" | | newContentStreamId | "user-cs-two-discarded" | - Then workspace user-ws-two has status OUTDATED - - When the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-ws-two" | - | rebasedContentStreamId | "user-cs-two-rebased-final" | - Then workspaces live,user-ws-one,user-ws-two have status UP_TO_DATE From b2c53f805e7237ef6d5d56b4ba3826e3816baedb Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:52:30 +0200 Subject: [PATCH 04/11] TASK: Remove legacy workspace state `UPDATE` logic --- .../DoctrineDbalContentGraphProjection.php | 20 ------- .../DoctrineDbalContentGraphSchemaBuilder.php | 1 - .../Domain/Projection/Feature/Workspace.php | 58 +------------------ .../SharedModel/Workspace/WorkspaceStatus.php | 12 ---- 4 files changed, 1 insertion(+), 90 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 527ed421d96..e6674618bfb 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -733,7 +733,6 @@ private function whenWorkspaceBaseWorkspaceWasChanged(WorkspaceBaseWorkspaceWasC private function whenWorkspaceRebaseFailed(WorkspaceRebaseFailed $event): void { - $this->markWorkspaceAsOutdatedConflict($event->workspaceName); $this->updateContentStreamStatus($event->candidateContentStreamId, ContentStreamStatus::REBASE_ERROR); } @@ -748,8 +747,6 @@ private function whenWorkspaceWasCreated(WorkspaceWasCreated $event): void private function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void { $this->updateWorkspaceContentStreamId($event->workspaceName, $event->newContentStreamId); - $this->markWorkspaceAsOutdated($event->workspaceName); - $this->markDependentWorkspacesAsOutdated($event->workspaceName); // the new content stream is in use now $this->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); @@ -760,7 +757,6 @@ private function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void private function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscarded $event): void { $this->updateWorkspaceContentStreamId($event->workspaceName, $event->newContentStreamId); - $this->markDependentWorkspacesAsOutdated($event->workspaceName); // the new content stream is in use now $this->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); @@ -773,12 +769,6 @@ private function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublish { // TODO: How do we test this method? – It's hard to design a BDD testcase that fails if this method is commented out... $this->updateWorkspaceContentStreamId($event->sourceWorkspaceName, $event->newSourceContentStreamId); - $this->markDependentWorkspacesAsOutdated($event->targetWorkspaceName); - - // NASTY: we need to set the source workspace name as non-outdated; as it has been made up-to-date again. - $this->markWorkspaceAsUpToDate($event->sourceWorkspaceName); - - $this->markDependentWorkspacesAsOutdated($event->sourceWorkspaceName); // the new content stream is in use now $this->updateContentStreamStatus($event->newSourceContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); @@ -791,12 +781,6 @@ private function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void { // TODO: How do we test this method? – It's hard to design a BDD testcase that fails if this method is commented out... $this->updateWorkspaceContentStreamId($event->sourceWorkspaceName, $event->newSourceContentStreamId); - $this->markDependentWorkspacesAsOutdated($event->targetWorkspaceName); - - // NASTY: we need to set the source workspace name as non-outdated; as it has been made up-to-date again. - $this->markWorkspaceAsUpToDate($event->sourceWorkspaceName); - - $this->markDependentWorkspacesAsOutdated($event->sourceWorkspaceName); // the new content stream is in use now $this->updateContentStreamStatus($event->newSourceContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); @@ -808,10 +792,6 @@ private function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void private function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void { $this->updateWorkspaceContentStreamId($event->workspaceName, $event->newContentStreamId); - $this->markDependentWorkspacesAsOutdated($event->workspaceName); - - // When the rebase is successful, we can set the status of the workspace back to UP_TO_DATE. - $this->markWorkspaceAsUpToDate($event->workspaceName); // the new content stream is in use now $this->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 05dd58f0ae7..08ca2f8919a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -115,7 +115,6 @@ private function createWorkspaceTable(): Table DbalSchemaFactory::columnForWorkspaceName('name')->setNotnull(true), DbalSchemaFactory::columnForWorkspaceName('baseWorkspaceName')->setNotnull(false), DbalSchemaFactory::columnForContentStreamId('currentContentStreamId')->setNotNull(true), - (new Column('status', self::type(Types::BINARY)))->setLength(20)->setNotnull(false), ]); return $workspaceTable->setPrimaryKey(['name']); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php index 5f1e744ef96..882e4469f96 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php @@ -4,7 +4,6 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceStatus; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -20,8 +19,7 @@ private function createWorkspace(WorkspaceName $workspaceName, ?WorkspaceName $b $this->dbal->insert($this->tableNames->workspace(), [ 'name' => $workspaceName->value, 'baseWorkspaceName' => $baseWorkspaceName?->value, - 'currentContentStreamId' => $contentStreamId->value, - 'status' => WorkspaceStatus::UP_TO_DATE->value + 'currentContentStreamId' => $contentStreamId->value ]); } @@ -55,58 +53,4 @@ private function updateWorkspaceContentStreamId( 'name' => $workspaceName->value ]); } - - private function markWorkspaceAsUpToDate(WorkspaceName $workspaceName): void - { - $this->dbal->executeStatement(' - UPDATE ' . $this->tableNames->workspace() . ' - SET status = :upToDate - WHERE - name = :workspaceName - ', [ - 'upToDate' => WorkspaceStatus::UP_TO_DATE->value, - 'workspaceName' => $workspaceName->value - ]); - } - - private function markDependentWorkspacesAsOutdated(WorkspaceName $baseWorkspaceName): void - { - $this->dbal->executeStatement(' - UPDATE ' . $this->tableNames->workspace() . ' - SET status = :outdated - WHERE - baseWorkspaceName = :baseWorkspaceName - ', [ - 'outdated' => WorkspaceStatus::OUTDATED->value, - 'baseWorkspaceName' => $baseWorkspaceName->value - ]); - } - - private function markWorkspaceAsOutdated(WorkspaceName $workspaceName): void - { - $this->dbal->executeStatement(' - UPDATE ' . $this->tableNames->workspace() . ' - SET - status = :outdated - WHERE - name = :workspaceName - ', [ - 'outdated' => WorkspaceStatus::OUTDATED->value, - 'workspaceName' => $workspaceName->value - ]); - } - - private function markWorkspaceAsOutdatedConflict(WorkspaceName $workspaceName): void - { - $this->dbal->executeStatement(' - UPDATE ' . $this->tableNames->workspace() . ' - SET - status = :outdatedConflict - WHERE - name = :workspaceName - ', [ - 'outdatedConflict' => WorkspaceStatus::OUTDATED_CONFLICT->value, - 'workspaceName' => $workspaceName->value - ]); - } } diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceStatus.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceStatus.php index 7c8d25067ff..5ef187bb1db 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceStatus.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceStatus.php @@ -47,18 +47,6 @@ enum WorkspaceStatus: string implements \JsonSerializable */ case OUTDATED = 'OUTDATED'; - /** - * CONFLICT Example: - * - * CONFLICT is a special case of OUTDATED, but then an error happens during the rebasing. - * - * Workspace Review <----------------------------------- Workspace User-Foo - * | . | - * Content Stream A2 (current) <-- Content Stream B2 (rebasing) | - * Content Stream B1 - */ - case OUTDATED_CONFLICT = 'OUTDATED_CONFLICT'; - public function equals(self $other): bool { return $this->value === $other->value; From 26e2de7104122c696a0e75bc77a5f6189200136b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 8 Oct 2024 00:15:48 +0200 Subject: [PATCH 05/11] TASK: Rename `sourceContentStreamVersion` column to be more precise ... and refactor status calculation logic. By leveraging `isSatisfiedBy` i found out the `>` comparison was not quite correct. We should use `===`. --- .../src/ContentRepositoryReadModelAdapter.php | 29 ++++++++++++------- .../DoctrineDbalContentGraphSchemaBuilder.php | 2 +- .../Projection/Feature/ContentStream.php | 4 +-- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php index 05bd545d25a..9fad838826b 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php @@ -30,6 +30,8 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceStatus; use Neos\EventStore\Model\Event\Version; +use Neos\EventStore\Model\EventStream\ExpectedVersion; +use Neos\EventStore\Model\EventStream\MaybeVersion; /** * @internal only used inside the @@ -55,11 +57,11 @@ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { $workspaceByNameStatement = <<tableNames->workspace()} ws JOIN {$this->tableNames->contentStream()} cs ON cs.id = ws.currentcontentstreamid - LEFT JOIN {$this->tableNames->contentStream()} scs ON cs.sourceContentStreamId = scs.id + LEFT JOIN {$this->tableNames->contentStream()} scs ON scs.id = cs.sourceContentStreamId WHERE ws.name = :workspaceName LIMIT 1 @@ -81,11 +83,11 @@ public function findWorkspaces(): Workspaces { $workspacesStatement = <<tableNames->workspace()} ws JOIN {$this->tableNames->contentStream()} cs ON cs.id = ws.currentcontentstreamid - LEFT JOIN {$this->tableNames->contentStream()} scs ON cs.sourceContentStreamId = scs.id + LEFT JOIN {$this->tableNames->contentStream()} scs ON scs.id = cs.sourceContentStreamId SQL; try { $rows = $this->dbal->fetchAllAssociative($workspacesStatement); @@ -140,16 +142,21 @@ public function findContentStreams(): ContentStreams */ private static function workspaceFromDatabaseRow(array $row): Workspace { - if ($row['sourceVersion'] === null) { - $status = WorkspaceStatus::UP_TO_DATE; - } elseif ($row['sourceVersion'] > $row['lastSourceVersion']) { - $status = WorkspaceStatus::OUTDATED; - } else { - $status = WorkspaceStatus::UP_TO_DATE; + $baseWorkspaceName = $row['baseWorkspaceName'] !== null ? WorkspaceName::fromString($row['baseWorkspaceName']) : null; + + $status = WorkspaceStatus::UP_TO_DATE; + if ($baseWorkspaceName !== null) { + // root workspaces can never be out of date and dont have a source content stream + $sourceVersion = MaybeVersion::fromVersionOrNull(Version::fromInteger($row['sourceVersion'])); + $expectedSourceVersion = ExpectedVersion::fromVersion(Version::fromInteger($row['expectedSourceVersion'])); + + if (!$expectedSourceVersion->isSatisfiedBy($sourceVersion)) { + $status = WorkspaceStatus::OUTDATED; + } } return new Workspace( WorkspaceName::fromString($row['name']), - isset($row['baseWorkspaceName']) ? WorkspaceName::fromString($row['baseWorkspaceName']) : null, + $baseWorkspaceName, ContentStreamId::fromString($row['currentContentStreamId']), $status, ); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 08ca2f8919a..0ce0ee51e2f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -126,10 +126,10 @@ private function createContentStreamTable(): Table DbalSchemaFactory::columnForContentStreamId('id')->setNotnull(true), (new Column('version', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForContentStreamId('sourceContentStreamId')->setNotnull(false), + (new Column('sourceContentStreamVersion', Type::getType(Types::INTEGER)))->setNotnull(false), // Should become a DB ENUM (unclear how to configure with DBAL) or int (latter needs adaption to code) (new Column('status', Type::getType(Types::BINARY)))->setLength(20)->setNotnull(true), (new Column('removed', Type::getType(Types::BOOLEAN)))->setDefault(false)->setNotnull(false), - (new Column('sourceVersion', Type::getType(Types::INTEGER)))->setNotnull(false), ]); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php index 8ee20873714..10a4392aca1 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php @@ -19,9 +19,9 @@ private function createContentStream(ContentStreamId $contentStreamId, ContentSt { $this->dbal->insert($this->tableNames->contentStream(), [ 'id' => $contentStreamId->value, - 'sourceContentStreamId' => $sourceContentStreamId?->value, 'version' => 0, - 'sourceVersion' => $sourceVersion?->value, + 'sourceContentStreamId' => $sourceContentStreamId?->value, + 'sourceContentStreamVersion' => $sourceVersion?->value, 'status' => $status->value, ]); } From e6a4d7ba9f095a86085b4c342098755873689595 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:14:52 +0200 Subject: [PATCH 06/11] TASK: Inline variable / select --- .../src/ContentGraphReadModelAdapter.php | 25 ++++++++----------- .../Features/Bootstrap/CRTestSuiteTrait.php | 2 +- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php index 70ff4445fc5..8870f6a51ec 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php @@ -18,8 +18,8 @@ use Doctrine\DBAL\Exception; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraph; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -30,8 +30,6 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceStatus; use Neos\EventStore\Model\Event\Version; -use Neos\EventStore\Model\EventStream\ExpectedVersion; -use Neos\EventStore\Model\EventStream\MaybeVersion; /** * @internal @@ -56,7 +54,7 @@ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { $workspaceByNameStatement = <<tableNames->workspace()} ws JOIN {$this->tableNames->contentStream()} cs ON cs.id = ws.currentcontentstreamid @@ -82,7 +80,7 @@ public function findWorkspaces(): Workspaces { $workspacesStatement = <<tableNames->workspace()} ws JOIN {$this->tableNames->contentStream()} cs ON cs.id = ws.currentcontentstreamid @@ -157,17 +155,14 @@ public function countNodes(): int private static function workspaceFromDatabaseRow(array $row): Workspace { $baseWorkspaceName = $row['baseWorkspaceName'] !== null ? WorkspaceName::fromString($row['baseWorkspaceName']) : null; + $status = match($row['baseWorkspaceChanged']) { + // no base workspace, a root is always up-to-date + null => WorkspaceStatus::UP_TO_DATE, + // base workspace didnt change (sql 0 is _false_) + 0 => WorkspaceStatus::UP_TO_DATE, + default => WorkspaceStatus::OUTDATED, + }; - $status = WorkspaceStatus::UP_TO_DATE; - if ($baseWorkspaceName !== null) { - // root workspaces can never be out of date and dont have a source content stream - $sourceVersion = MaybeVersion::fromVersionOrNull(Version::fromInteger($row['sourceVersion'])); - $expectedSourceVersion = ExpectedVersion::fromVersion(Version::fromInteger($row['expectedSourceVersion'])); - - if (!$expectedSourceVersion->isSatisfiedBy($sourceVersion)) { - $status = WorkspaceStatus::OUTDATED; - } - } return new Workspace( WorkspaceName::fromString($row['name']), $baseWorkspaceName, diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php index fcfd610f4ed..3d04885b79e 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -152,7 +152,7 @@ public function workspaceStatusMatchesExpected(string $rawWorkspaceNames, string foreach ($rawWorkspaceNames as $rawWorkspaceName) { $workspace = $this->currentContentRepository->findWorkspaceByName(WorkspaceName::fromString($rawWorkspaceName)); Assert::assertNotNull($workspace, "Workspace $rawWorkspaceName does not exist."); - Assert::assertEquals($status, $workspace->status->value, "Workspace $rawWorkspaceName has unexpected status."); + Assert::assertEquals($status, $workspace->status->value, "Workspace '$rawWorkspaceName' has unexpected status."); } } From 665a1b69d2c00df18ccf42f7f7ebaf10a1518887 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:22:30 +0200 Subject: [PATCH 07/11] TASK: Use `QueryBuilder` to share query --- .../src/ContentGraphReadModelAdapter.php | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php index 8870f6a51ec..cd73ac43b51 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php @@ -16,6 +16,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Query\QueryBuilder; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraph; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; @@ -52,21 +53,12 @@ public function buildContentGraph(WorkspaceName $workspaceName, ContentStreamId public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { - $workspaceByNameStatement = <<tableNames->workspace()} ws - JOIN {$this->tableNames->contentStream()} cs ON cs.id = ws.currentcontentstreamid - LEFT JOIN {$this->tableNames->contentStream()} scs ON scs.id = cs.sourceContentStreamId - WHERE - ws.name = :workspaceName - LIMIT 1 - SQL; + $workspaceQuery = $this->getBasicWorkspaceQuery() + ->where('ws.name = :workspaceName') + ->setMaxResults(1) + ->setParameter('workspaceName', $workspaceName->value); try { - $row = $this->dbal->fetchAssociative($workspaceByNameStatement, [ - 'workspaceName' => $workspaceName->value, - ]); + $row = $workspaceQuery->fetchAssociative(); } catch (Exception $e) { throw new \RuntimeException(sprintf('Failed to load workspace from database: %s', $e->getMessage()), 1716486077, $e); } @@ -78,16 +70,9 @@ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace public function findWorkspaces(): Workspaces { - $workspacesStatement = <<tableNames->workspace()} ws - JOIN {$this->tableNames->contentStream()} cs ON cs.id = ws.currentcontentstreamid - LEFT JOIN {$this->tableNames->contentStream()} scs ON scs.id = cs.sourceContentStreamId - SQL; + $workspacesQuery = $this->getBasicWorkspaceQuery(); try { - $rows = $this->dbal->fetchAllAssociative($workspacesStatement); + $rows = $workspacesQuery->fetchAllAssociative(); } catch (Exception $e) { throw new \RuntimeException(sprintf('Failed to load workspaces from database: %s', $e->getMessage()), 1716902981, $e); } @@ -149,6 +134,17 @@ public function countNodes(): int } } + private function getBasicWorkspaceQuery(): QueryBuilder + { + $queryBuilder = $this->dbal->createQueryBuilder(); + + return $queryBuilder + ->select('ws.name, ws.baseWorkspaceName, ws.currentContentStreamId, cs.sourceContentStreamVersion != scs.version as baseWorkspaceChanged') + ->from($this->tableNames->workspace(), 'ws') + ->join('ws', $this->tableNames->contentStream(), 'cs', 'cs.id = ws.currentcontentstreamid') + ->leftJoin('cs', $this->tableNames->contentStream(), 'scs', 'scs.id = cs.sourceContentStreamId'); + } + /** * @param array $row */ From bd44b74ca0bb82124b04f6745f6ca2727afd69ef Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:38:17 +0200 Subject: [PATCH 08/11] TASK: Introduce ContentGraphReadModelInterface::getContentGraph to avoid fetching full workspace model with the extra joins in the workspace model it is just easier to just select the current content stream id directly --- .../src/ContentGraphReadModelAdapter.php | 26 +++++++++++++++++++ .../src/ContentHyperGraphReadModelAdapter.php | 10 +++++++ .../Classes/ContentRepository.php | 6 +---- .../ContentGraphReadModelInterface.php | 9 +++++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php index cd73ac43b51..3c1356f5483 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php @@ -22,6 +22,7 @@ use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; @@ -46,6 +47,31 @@ public function __construct( ) { } + public function getContentGraph(WorkspaceName $workspaceName): ContentGraph + { + $currentContentStreamIdStatement = <<tableNames->workspace()} + WHERE + name = :workspaceName + LIMIT 1 + SQL; + try { + $row = $this->dbal->fetchAssociative($currentContentStreamIdStatement, [ + 'workspaceName' => $workspaceName->value, + ]); + } catch (Exception $e) { + throw new \RuntimeException(sprintf('Failed to load current content stream id from database: %s', $e->getMessage()), 1716903166, $e); + } + if ($row === false) { + throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); + } + $currentContentStreamId = ContentStreamId::fromString($row['currentContentStreamId']); + return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $currentContentStreamId); + } + public function buildContentGraph(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraph { return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $contentStreamId); diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphReadModelAdapter.php b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphReadModelAdapter.php index d4d68465df2..9086e9dc5af 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphReadModelAdapter.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphReadModelAdapter.php @@ -11,6 +11,7 @@ use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; @@ -32,6 +33,15 @@ public function __construct( ) { } + public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface + { + $contentStreamId = $this->findWorkspaceByName($workspaceName)?->currentContentStreamId; + if ($contentStreamId === null) { + throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); + } + return new ContentHyperGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNamePrefix, $workspaceName, $contentStreamId); + } + public function buildContentGraph(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface { return new ContentHyperGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNamePrefix, $workspaceName, $contentStreamId); diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 696b2de305d..00f8a825620 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -257,11 +257,7 @@ public function resetProjectionState(string $projectionClassName): void */ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { - $workspace = $this->contentGraphReadModel->findWorkspaceByName($workspaceName); - if ($workspace === null) { - throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); - } - return $this->contentGraphReadModel->buildContentGraph($workspaceName, $workspace->currentContentStreamId); + return $this->contentGraphReadModel->getContentGraph($workspaceName); } /** diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphReadModelInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphReadModelInterface.php index 5902b8aba00..029046c7e98 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphReadModelInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphReadModelInterface.php @@ -15,6 +15,7 @@ namespace Neos\ContentRepository\Core\Projection\ContentGraph; use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; @@ -27,6 +28,14 @@ */ interface ContentGraphReadModelInterface extends ProjectionStateInterface { + /** + * @throws WorkspaceDoesNotExist if the workspace does not exist + */ + public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface; + + /** + * @deprecated todo remove me after https://github.com/neos/neos-development-collection/pull/5301 ;) + */ public function buildContentGraph(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface; public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace; From 5cdd2a150da5e983721718b52988742c17acce26 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:51:54 +0200 Subject: [PATCH 09/11] =?UTF-8?q?Linte,=20Linte=20ging=20in=20Laden=20woll?= =?UTF-8?q?te=20ein=20st=C3=BCck=20k=C3=A4se=20haben?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/ContentGraphReadModelAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php index 3c1356f5483..78adaf33be4 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php @@ -177,7 +177,7 @@ private function getBasicWorkspaceQuery(): QueryBuilder private static function workspaceFromDatabaseRow(array $row): Workspace { $baseWorkspaceName = $row['baseWorkspaceName'] !== null ? WorkspaceName::fromString($row['baseWorkspaceName']) : null; - $status = match($row['baseWorkspaceChanged']) { + $status = match ($row['baseWorkspaceChanged']) { // no base workspace, a root is always up-to-date null => WorkspaceStatus::UP_TO_DATE, // base workspace didnt change (sql 0 is _false_) From 233d49b194f50587f9de5e5fbe3898db2487ec82 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:43:38 +0200 Subject: [PATCH 10/11] Add todo regarding caching --- .../Projection/ContentGraph/ContentGraphReadModelInterface.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphReadModelInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphReadModelInterface.php index 029046c7e98..03bce7a58b7 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphReadModelInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphReadModelInterface.php @@ -30,6 +30,7 @@ interface ContentGraphReadModelInterface extends ProjectionStateInterface { /** * @throws WorkspaceDoesNotExist if the workspace does not exist + * todo cache instances to reduce queries (revert https://github.com/neos/neos-development-collection/pull/5246) */ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface; From b8a2bce701a1b29ebb93403a4a6a5d86a65ff854 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:48:53 +0200 Subject: [PATCH 11/11] Add todo regarding exposing and using source content stream version --- .../src/ContentGraphReadModelAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php index 78adaf33be4..f1614583730 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php @@ -194,7 +194,7 @@ private static function workspaceFromDatabaseRow(array $row): Workspace } /** - * @param array $row + * @param array $row todo fetch source content stream version and use for publishing as expected version */ private static function contentStreamFromDatabaseRow(array $row): ContentStream {