diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php index 66ae7720899..50686e2bdbf 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php @@ -39,16 +39,9 @@ public function __construct( public function buildForWorkspace(WorkspaceName $workspaceName): ContentGraph { - // FIXME: Should be part of this projection, this is forbidden - $tableName = strtolower(sprintf( - 'cr_%s_p_%s', - $this->contentRepositoryId->value, - 'workspace' - )); - $row = $this->client->getConnection()->executeQuery( ' - SELECT * FROM ' . $tableName . ' + SELECT * FROM ' . $this->tableNames->workspaces() . ' WHERE workspaceName = :workspaceName LIMIT 1 ', diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php index 0804d77e7e0..90bea9c8ecc 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -40,6 +40,11 @@ public function referenceRelation(): string return $this->tableNamePrefix . '_referencerelation'; } + public function workspaces(): string + { + return $this->tableNamePrefix . '_workspaces'; + } + public function checkpoint(): string { return $this->tableNamePrefix . '_checkpoint'; diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index d97e80194c7..80c0ed9695d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -7,6 +7,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Types\Types; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\CurrentWorkspaceContentStreamId; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeMove; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeRemoval; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeVariation; @@ -43,6 +44,15 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTags; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasTagged; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasUntagged; +use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Event\RootWorkspaceWasCreated; +use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Event\WorkspaceWasCreated; +use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceBaseWorkspaceWasChanged; +use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceWasRemoved; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasDiscarded; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPartiallyDiscarded; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPartiallyPublished; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPublished; +use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Infrastructure\DbalCheckpointStorage; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; use Neos\ContentRepository\Core\Infrastructure\DbalSchemaDiff; @@ -69,7 +79,7 @@ final class DoctrineDbalContentGraphProjection implements ProjectionInterface use SubtreeTagging; use NodeRemoval; use NodeMove; - + use CurrentWorkspaceContentStreamId; public const RELATION_DEFAULT_OFFSET = 128; @@ -157,6 +167,7 @@ private function truncateDatabaseTables(): void $connection->executeQuery('TRUNCATE table ' . $this->tableNames->hierarchyRelation()); $connection->executeQuery('TRUNCATE table ' . $this->tableNames->referenceRelation()); $connection->executeQuery('TRUNCATE table ' . $this->tableNames->dimensionSpacePoints()); + $connection->executeQuery('TRUNCATE table ' . $this->tableNames->workspaces()); } public function canHandle(EventInterface $event): bool @@ -180,6 +191,21 @@ public function canHandle(EventInterface $event): bool NodePeerVariantWasCreated::class, SubtreeWasTagged::class, SubtreeWasUntagged::class, + + /** + * Workspace related commands, see {@see CurrentWorkspaceContentStreamId} + * We are not interested in the events WorkspaceWasRenamed, WorkspaceRebaseFailed and WorkspaceOwnerWasChanged + * As they do not change the current content stream id + */ + WorkspaceWasCreated::class, + RootWorkspaceWasCreated::class, + WorkspaceWasDiscarded::class, + WorkspaceWasPartiallyDiscarded::class, + WorkspaceWasPartiallyPublished::class, + WorkspaceWasPublished::class, + WorkspaceWasRebased::class, + WorkspaceWasRemoved::class, + WorkspaceBaseWorkspaceWasChanged::class, ]); } @@ -204,6 +230,15 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void NodePeerVariantWasCreated::class => $this->whenNodePeerVariantWasCreated($event, $eventEnvelope), SubtreeWasTagged::class => $this->whenSubtreeWasTagged($event), SubtreeWasUntagged::class => $this->whenSubtreeWasUntagged($event), + WorkspaceWasCreated::class => $this->whenWorkspaceWasCreated($event), + RootWorkspaceWasCreated::class => $this->whenRootWorkspaceWasCreated($event), + WorkspaceWasDiscarded::class => $this->whenWorkspaceWasDiscarded($event), + WorkspaceWasPartiallyDiscarded::class => $this->whenWorkspaceWasPartiallyDiscarded($event), + WorkspaceWasPartiallyPublished::class => $this->whenWorkspaceWasPartiallyPublished($event), + WorkspaceWasPublished::class => $this->whenWorkspaceWasPublished($event), + WorkspaceWasRebased::class => $this->whenWorkspaceWasRebased($event), + WorkspaceWasRemoved::class => $this->whenWorkspaceWasRemoved($event), + WorkspaceBaseWorkspaceWasChanged::class => $this->whenWorkspaceBaseWorkspaceWasChanged($event), default => throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), }; } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index c8520724f01..61aebfc01b8 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -18,7 +18,7 @@ class DoctrineDbalContentGraphSchemaBuilder private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci'; public function __construct( - private readonly ContentGraphTableNames $contentGraphTableNames + private readonly ContentGraphTableNames $tableNames ) { } @@ -28,13 +28,14 @@ public function buildSchema(AbstractSchemaManager $schemaManager): Schema $this->createNodeTable(), $this->createHierarchyRelationTable(), $this->createReferenceRelationTable(), - $this->createDimensionSpacePointsTable() + $this->createDimensionSpacePointsTable(), + $this->createWorkspacesTable() ]); } private function createNodeTable(): Table { - $table = new Table($this->contentGraphTableNames->node(), [ + $table = new Table($this->tableNames->node(), [ DbalSchemaFactory::columnForNodeAnchorPoint('relationanchorpoint')->setAutoincrement(true), DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotnull(false), DbalSchemaFactory::columnForDimensionSpacePointHash('origindimensionspacepointhash')->setNotnull(false), @@ -55,7 +56,7 @@ private function createNodeTable(): Table private function createHierarchyRelationTable(): Table { - $table = new Table($this->contentGraphTableNames->hierarchyRelation(), [ + $table = new Table($this->tableNames->hierarchyRelation(), [ (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'), (new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotnull(true), @@ -75,7 +76,7 @@ private function createHierarchyRelationTable(): Table private function createDimensionSpacePointsTable(): Table { - $table = new Table($this->contentGraphTableNames->dimensionSpacePoints(), [ + $table = new Table($this->tableNames->dimensionSpacePoints(), [ DbalSchemaFactory::columnForDimensionSpacePointHash('hash')->setNotnull(true), DbalSchemaFactory::columnForDimensionSpacePoint('dimensionspacepoint')->setNotnull(true) ]); @@ -86,7 +87,7 @@ private function createDimensionSpacePointsTable(): Table private function createReferenceRelationTable(): Table { - $table = new Table($this->contentGraphTableNames->referenceRelation(), [ + $table = new Table($this->tableNames->referenceRelation(), [ (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'), (new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForNodeAnchorPoint('nodeanchorpoint'), @@ -97,4 +98,14 @@ private function createReferenceRelationTable(): Table return $table ->setPrimaryKey(['name', 'position', 'nodeanchorpoint']); } + + private function createWorkspacesTable(): Table + { + $workspaceTable = new Table($this->tableNames->workspaces(), [ + (new Column('workspacename', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + DbalSchemaFactory::columnForContentStreamId('currentcontentstreamid')->setNotNull(true), + ]); + + return $workspaceTable->setPrimaryKey(['workspacename']); + } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/CurrentWorkspaceContentStreamId.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/CurrentWorkspaceContentStreamId.php new file mode 100644 index 00000000000..9c1cf936c1d --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/CurrentWorkspaceContentStreamId.php @@ -0,0 +1,96 @@ +getDatabaseConnection()->insert($this->tableNames->workspaces(), [ + 'workspaceName' => $event->workspaceName->value, + 'currentContentStreamId' => $event->newContentStreamId->value + ]); + } + + private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): void + { + $this->getDatabaseConnection()->insert($this->tableNames->workspaces(), [ + 'workspaceName' => $event->workspaceName->value, + 'currentContentStreamId' => $event->newContentStreamId->value + ]); + } + + private function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void + { + $this->updateContentStreamId($event->newContentStreamId, $event->workspaceName); + } + + private function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscarded $event): void + { + $this->updateContentStreamId($event->newContentStreamId, $event->workspaceName); + } + + private function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublished $event): void + { + $this->updateContentStreamId($event->newSourceContentStreamId, $event->sourceWorkspaceName); + } + + private function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void + { + $this->updateContentStreamId($event->newSourceContentStreamId, $event->sourceWorkspaceName); + } + + private function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void + { + $this->updateContentStreamId($event->newContentStreamId, $event->workspaceName); + } + + private function whenWorkspaceWasRemoved(WorkspaceWasRemoved $event): void + { + $this->getDatabaseConnection()->delete( + $this->tableNames->workspaces(), + ['workspaceName' => $event->workspaceName->value] + ); + } + + private function whenWorkspaceBaseWorkspaceWasChanged(WorkspaceBaseWorkspaceWasChanged $event): void + { + $this->updateContentStreamId($event->newContentStreamId, $event->workspaceName); + } + + private function updateContentStreamId( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + ): void { + $this->getDatabaseConnection()->update($this->tableNames->workspaces(), [ + 'currentContentStreamId' => $contentStreamId->value, + ], [ + 'workspaceName' => $workspaceName->value + ]); + } +}