From 0f2b46501bcb51ed174c2b52b5e7a95fa25e227e Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:36:19 +0200 Subject: [PATCH 01/17] WIP: Introduce ContentStreamDbIds to handle one cs with multiple db ids --- .../src/ContentGraphReadModelAdapter.php | 18 +-- .../src/ContentGraphTableNames.php | 5 + .../DoctrineDbalContentGraphProjection.php | 63 ++++---- ...trineDbalContentGraphProjectionFactory.php | 4 +- .../DoctrineDbalContentGraphSchemaBuilder.php | 17 +- .../Domain/Projection/ContentStreamDbId.php | 6 +- .../Domain/Projection/ContentStreamDbIds.php | 65 ++++++++ .../Projection/Feature/ContentStream.php | 3 + .../Domain/Projection/Feature/NodeRemoval.php | 6 +- .../Projection/Feature/SubtreeTagging.php | 63 ++++---- .../src/Domain/Repository/ContentGraph.php | 62 +++++--- .../Repository/ContentStreamDbIdFinder.php | 27 ++-- .../src/Domain/Repository/ContentSubgraph.php | 70 +++++---- .../Repository/ProjectionContentGraph.php | 146 +++++++++++------- .../src/NodeQueryBuilder.php | 42 ++--- 15 files changed, 370 insertions(+), 227 deletions(-) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbIds.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php index 2bef2ecc699..174964311e7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php @@ -17,7 +17,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Query\QueryBuilder; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraph; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; @@ -51,27 +51,27 @@ public function getContentGraph(WorkspaceName $workspaceName): ContentGraph $currentContentStreamIdStatement = <<tableNames->workspace()} ws - JOIN {$this->tableNames->contentStream()} cs ON cs.id = ws.currentContentStreamId + JOIN {$this->tableNames->contentStreamId()} csIds ON csIds.id = ws.currentContentStreamId WHERE ws.name = :workspaceName - LIMIT 1 SQL; try { - $row = $this->dbal->fetchAssociative($currentContentStreamIdStatement, [ + $rows = $this->dbal->fetchAllAssociative($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) { + if ($rows === []) { throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); } - $currentContentStreamId = ContentStreamId::fromString($row['currentContentStreamId']); - $contentStreamDbId = ContentStreamDbId::fromInt((int)$row['contentStreamDbId']); - return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $currentContentStreamId, $contentStreamDbId); + $firstRow = reset($rows); + $currentContentStreamId = ContentStreamId::fromString($firstRow['currentContentStreamId']); + $contentStreamDbIds = ContentStreamDbIds::fromArray(array_column($rows, 'contentStreamDbId')); + return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $currentContentStreamId, $contentStreamDbIds); } public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php index 51f14b392cc..187ae7fe180 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -51,4 +51,9 @@ public function contentStream(): string { return $this->tableNamePrefix . '_contentstream'; } + + public function contentStreamId(): string + { + return $this->tableNamePrefix . '_contentstreamid'; + } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index cf76c111067..7155168cc41 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -7,7 +7,7 @@ use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception as DBALException; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\ContentStream; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeMove; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeRemoval; @@ -245,8 +245,8 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void SQL; try { $this->dbal->executeStatement($insertRelationStatement, [ - 'newContentStreamDbId' => $newContentStreamDbId->value, - 'sourceContentStreamDbId' => $sourceContentStreamDbId->value + 'newContentStreamDbId' => $newContentStreamDbId->current()->value, + 'sourceContentStreamDbId' => $sourceContentStreamDbId->current()->value ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to insert hierarchy relation: %s', $e->getMessage()), 1716489211, $e); @@ -258,15 +258,15 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): void { - $contentStreamDbId = $this->contentStreamDbIdFinder->getContentStreamDbId($event->contentStreamId); + $contentStreamDbIds = $this->contentStreamDbIdFinder->getContentStreamDbId($event->contentStreamId); // Drop hierarchy relations $deleteHierarchyRelationStatement = <<tableNames->hierarchyRelation()} WHERE contentstreamdbid = :contentStreamDbId + DELETE FROM {$this->tableNames->hierarchyRelation()} WHERE contentstreamdbid IN (:contentStreamDbIds) SQL; try { $this->dbal->executeStatement($deleteHierarchyRelationStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray() ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to delete hierarchy relations: %s', $e->getMessage()), 1716489265, $e); @@ -331,7 +331,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded h.contentstreamdbid FROM {$this->tableNames->hierarchyRelation()} h - WHERE h.contentstreamdbid = :contentStreamDbId + WHERE h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash = :sourceDimensionSpacePointHash SQL; try { @@ -358,7 +358,7 @@ private function whenDimensionSpacePointWasMoved(DimensionSpacePointWasMoved $ev FROM {$this->tableNames->node()} n INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash = :dimensionSpacePointHash -- find only nodes which have their ORIGIN at the source DimensionSpacePoint, -- as we need to rewrite these origins (using copy on write) @@ -391,7 +391,7 @@ function (NodeRecord $nodeRecord) use ($event) { h.dimensionspacepointhash = :newDimensionSpacePointHash WHERE h.dimensionspacepointhash = :originalDimensionSpacePointHash - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) SQL; try { $this->dbal->executeStatement($updateHierarchyRelationsStatement, [ @@ -728,7 +728,7 @@ private function whenWorkspaceWasRemoved(WorkspaceWasRemoved $event): void /** --------------------------------- */ - public function getContentStreamDbId(EmbedsContentStreamId $event): ContentStreamDbId + public function getContentStreamDbId(EmbedsContentStreamId $event): ContentStreamDbIds { return $this->contentStreamDbIdFinder->getContentStreamDbId($event->getContentStreamId()); } @@ -751,6 +751,7 @@ private function truncateDatabaseTables(): void $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->dimensionSpacePoints()); $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->workspace()); $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->contentStream()); + $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->contentStreamId()); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to truncate database tables for projection %s: %s', self::class, $e->getMessage()), 1716478318, $e); } @@ -762,7 +763,7 @@ private function truncateDatabaseTables(): void * @template T */ private function updateNodeRecordWithCopyOnWrite( - ContentStreamDbId $contentStreamDbIdWhereWriteOccurs, + ContentStreamDbIds $contentStreamDbIdsWhereWriteOccurs, NodeRelationAnchorPoint $anchorPoint, callable $operations ): mixed { @@ -791,13 +792,13 @@ private function updateNodeRecordWithCopyOnWrite( h.parentnodeanchor = IF(h.parentnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.parentnodeanchor) WHERE :originalNodeAnchor IN (h.childnodeanchor, h.parentnodeanchor) - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) SQL; try { $this->dbal->executeStatement($updateHierarchyRelationStatement, [ 'newNodeAnchor' => $copiedNode->relationAnchorPoint->value, 'originalNodeAnchor' => $anchorPoint->value, - 'contentStreamDbId' => $contentStreamDbIdWhereWriteOccurs->value, + 'contentStreamDbId' => $contentStreamDbIdsWhereWriteOccurs->value, ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to update hierarchy relation: %s', $e->getMessage()), 1716486444, $e); @@ -864,7 +865,7 @@ private static function initiatingDateTime(EventEnvelope $eventEnvelope): \DateT } private function createNodeWithHierarchy( - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, NodeTypeName $nodeTypeName, NodeAggregateId $parentNodeAggregateId, @@ -902,7 +903,7 @@ private function createNodeWithHierarchy( foreach ($missingParentRelations as $dimensionSpacePoint) { $parentNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamDbIds, $parentNodeAggregateId, $dimensionSpacePoint ); @@ -910,7 +911,7 @@ private function createNodeWithHierarchy( $succeedingSiblingNodeAggregateId = $coverageSucceedingSiblings->getSucceedingSiblingIdForDimensionSpacePoint($dimensionSpacePoint); $succeedingSibling = $succeedingSiblingNodeAggregateId ? $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamDbIds, $succeedingSiblingNodeAggregateId, $dimensionSpacePoint ) @@ -918,7 +919,7 @@ private function createNodeWithHierarchy( if ($parentNode) { $this->connectHierarchy( - $contentStreamDbId, + $contentStreamDbIds, $parentNode->relationAnchorPoint, $node->relationAnchorPoint, new DimensionSpacePointSet([$dimensionSpacePoint]), @@ -932,7 +933,7 @@ private function createNodeWithHierarchy( } private function connectHierarchy( - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, NodeRelationAnchorPoint $parentNodeAnchorPoint, NodeRelationAnchorPoint $childNodeAnchorPoint, DimensionSpacePointSet $dimensionSpacePointSet, @@ -943,17 +944,17 @@ private function connectHierarchy( $parentNodeAnchorPoint, null, $succeedingSiblingNodeAnchorPoint, - $contentStreamDbId, + $contentStreamDbIds, $dimensionSpacePoint ); - $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamDbId, $parentNodeAnchorPoint, $dimensionSpacePoint); + $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamDbIds, $parentNodeAnchorPoint, $dimensionSpacePoint); $inheritedSubtreeTags = NodeTags::create(SubtreeTags::createEmpty(), $parentSubtreeTags->all()); $hierarchyRelation = new HierarchyRelation( $parentNodeAnchorPoint, $childNodeAnchorPoint, - $contentStreamDbId, + $contentStreamDbIds->current(), $dimensionSpacePoint, $dimensionSpacePoint->hash, $position, @@ -968,14 +969,14 @@ private function getRelationPosition( ?NodeRelationAnchorPoint $parentAnchorPoint, ?NodeRelationAnchorPoint $childAnchorPoint, ?NodeRelationAnchorPoint $succeedingSiblingAnchorPoint, - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint ): int { $position = $this->projectionContentGraph->determineHierarchyRelationPosition( $parentAnchorPoint, $childAnchorPoint, $succeedingSiblingAnchorPoint, - $contentStreamDbId, + $contentStreamDbIds, $dimensionSpacePoint ); @@ -984,7 +985,7 @@ private function getRelationPosition( $parentAnchorPoint, $childAnchorPoint, $succeedingSiblingAnchorPoint, - $contentStreamDbId, + $contentStreamDbIds, $dimensionSpacePoint ); } @@ -996,7 +997,7 @@ private function getRelationPositionAfterRecalculation( ?NodeRelationAnchorPoint $parentAnchorPoint, ?NodeRelationAnchorPoint $childAnchorPoint, ?NodeRelationAnchorPoint $succeedingSiblingAnchorPoint, - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint ): int { if (!$childAnchorPoint && !$parentAnchorPoint) { @@ -1011,12 +1012,12 @@ private function getRelationPositionAfterRecalculation( $hierarchyRelations = $parentAnchorPoint ? $this->projectionContentGraph->getOutgoingHierarchyRelationsForNodeAndSubgraph( $parentAnchorPoint, - $contentStreamDbId, + $contentStreamDbIds, $dimensionSpacePoint ) : $this->projectionContentGraph->getIngoingHierarchyRelationsForNodeAndSubgraph( $childAnchorPoint, - $contentStreamDbId, + $contentStreamDbIds, $dimensionSpacePoint ); @@ -1042,25 +1043,25 @@ private function getRelationPositionAfterRecalculation( private function copyHierarchyRelationToDimensionSpacePoint( HierarchyRelation $sourceHierarchyRelation, - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint, NodeRelationAnchorPoint $newParent, NodeRelationAnchorPoint $newChild, ?NodeRelationAnchorPoint $newSucceedingSibling = null, ): HierarchyRelation { - $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamDbId, $newParent, $dimensionSpacePoint); + $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamDbIds, $newParent, $dimensionSpacePoint); $inheritedSubtreeTags = NodeTags::create($sourceHierarchyRelation->subtreeTags->withoutInherited()->all(), $parentSubtreeTags->withoutInherited()->all()); $copy = new HierarchyRelation( $newParent, $newChild, - $contentStreamDbId, + $contentStreamDbIds->current(), $dimensionSpacePoint, $dimensionSpacePoint->hash, $this->getRelationPosition( $newParent, $newChild, $newSucceedingSibling, - $contentStreamDbId, + $contentStreamDbIds, $dimensionSpacePoint ), $inheritedSubtreeTags, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php index 7a4a54daf01..a381d217a90 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php @@ -32,7 +32,7 @@ public function build( ); $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbal, $tableNames); - $contentStreamDbIdRepository = new ContentStreamDbIdFinder($this->dbal, $tableNames); + $contentStreamDbIdsRepository = new ContentStreamDbIdFinder($this->dbal, $tableNames); $nodeFactory = new NodeFactory( $projectionFactoryDependencies->contentRepositoryId, @@ -56,7 +56,7 @@ public function build( ), $tableNames, $dimensionSpacePointsRepository, - $contentStreamDbIdRepository, + $contentStreamDbIdsRepository, $contentGraphReadModel ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 47832978d56..9f269183ae7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -33,6 +33,7 @@ public function buildSchema(Connection $connection): Schema $this->createDimensionSpacePointsTable($connection->getDatabasePlatform()), $this->createWorkspaceTable($connection->getDatabasePlatform()), $this->createContentStreamTable($connection->getDatabasePlatform()), + $this->createContentStreamIdTable($connection->getDatabasePlatform()), ]); } @@ -120,7 +121,6 @@ private function createWorkspaceTable(AbstractPlatform $platform): Table private function createContentStreamTable(AbstractPlatform $platform): Table { $contentStreamTable = self::createTable($this->tableNames->contentStream(), [ - (new Column('dbId', Type::getType(Types::INTEGER)))->setAutoincrement(true)->setNotnull(true), DbalSchemaFactory::columnForContentStreamId('id', $platform)->setNotnull(true), (new Column('version', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForContentStreamId('sourceContentStreamId', $platform)->setNotnull(false), @@ -130,8 +130,19 @@ private function createContentStreamTable(AbstractPlatform $platform): Table ]); return $contentStreamTable - ->addUniqueIndex(['id']) - ->setPrimaryKey(['dbId']); + ->setPrimaryKey(['id']); + } + + private function createContentStreamIdTable(AbstractPlatform $platform): Table + { + $contentStreamIdTable = self::createTable($this->tableNames->contentStreamId(), [ + DbalSchemaFactory::columnForContentStreamId('id', $platform)->setNotnull(true), + (new Column('dbId', Type::getType(Types::INTEGER)))->setAutoincrement(true)->setNotnull(true), + ]); + + return $contentStreamIdTable + ->addUniqueIndex(['dbId']) + ->setPrimaryKey(['id', 'dbId']); } /** diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbId.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbId.php index 8efc1ef1499..ecce233283c 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbId.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbId.php @@ -4,12 +4,8 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; - /** - * @internal adapter specific id for content streams. - * Auto-incrementing int to optimise schema vs unconstrained cr content-stream-ids. - * Each {@see ContentStreamDbId} points to exactly one {@see ContentStreamId}. + * @internal */ final readonly class ContentStreamDbId { diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbIds.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbIds.php new file mode 100644 index 00000000000..8970dd19c4c --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbIds.php @@ -0,0 +1,65 @@ + $items + */ + private function __construct( + // todo remove all usages + // public int $value, + private ContentStreamDbId $max, + public array $items + ) { + } + + public static function from(ContentStreamDbId ...$items): self + { + if ($items === []) { + throw new \InvalidArgumentException('Db ids must not be empty', 1775819046); + } + $max = []; + $indexed = []; + foreach ($items as $id) { + $indexed[$id->value] = $id; + $max[] = $id->value; + } + return new self( + max: $indexed[max($max)], + items: $indexed, + ); + } + + /** @param array $array */ + public static function fromArray(array $array): self + { + return self::from( + ...array_map(ContentStreamDbId::fromInt(...), $array), + ); + } + + public function current(): ContentStreamDbId + { + return $this->max; + } + + /** + * @return list + */ + public function toIntArray(): array + { + return array_map(fn (ContentStreamDbId $id) => $id->value, $this->items); + } + + public function toDebugString(): string + { + return sprintf('DbIds[%s]', join(',', $this->toIntArray())); + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php index 08e2e151824..e10b23e3b99 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php @@ -24,6 +24,9 @@ private function createContentStream(ContentStreamId $contentStreamId, ?ContentS 'closed' => 0, 'hasChanges' => 0 ]); + $this->dbal->insert($this->tableNames->contentStreamId(), [ + 'id' => $contentStreamId->value, + ]); } private function closeContentStream(ContentStreamId $contentStreamId): void diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php index 6e7b72fc2ca..e248b9ce7f7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php @@ -5,7 +5,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature; use Doctrine\DBAL\Exception as DBALException; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; @@ -17,12 +17,12 @@ */ trait NodeRemoval { - private function removeNodeAggregate(ContentStreamDbId $contentStreamDbId, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedCoveredDimensionSpacePoints): void + private function removeNodeAggregate(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedCoveredDimensionSpacePoints): void { // the focus here is to be correct; that's why the method is not overly performant (for now at least). We might // lateron find tricks to improve performance $ingoingRelations = $this->projectionContentGraph->findIngoingHierarchyRelationsForNodeAggregate( - $contentStreamDbId, + $contentStreamDbIds, $nodeAggregateId, $affectedCoveredDimensionSpacePoints ); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 1ea90e3f401..04d8b96218e 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -6,7 +6,7 @@ use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Exception as DBALException; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; @@ -22,7 +22,7 @@ */ trait SubtreeTagging { - private function addSubtreeTag(ContentStreamDbId $contentStreamDbId, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedDimensionSpacePoints, SubtreeTag $tag): void + private function addSubtreeTag(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedDimensionSpacePoints, SubtreeTag $tag): void { $addTagToDescendantsStatement = <<tableNames->hierarchyRelation()} h @@ -33,7 +33,7 @@ private function addSubtreeTag(ContentStreamDbId $contentStreamDbId, NodeAggrega INNER JOIN {$this->tableNames->node()} n ON n.relationanchorpoint = ch.parentnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId - AND ch.contentstreamdbid = :contentStreamDbId + AND ch.contentstreamdbid IN (:contentStreamDbIds) AND ch.dimensionspacepointhash in (:dimensionSpacePointHashes) AND NOT JSON_CONTAINS_PATH(ch.subtreetags, 'one', :tagPath) UNION ALL @@ -43,7 +43,7 @@ private function addSubtreeTag(ContentStreamDbId $contentStreamDbId, NodeAggrega FROM cte JOIN {$this->tableNames->hierarchyRelation()} dh ON dh.parentnodeanchor = cte.id - AND dh.contentstreamdbid = :contentStreamDbId + AND dh.contentstreamdbid IN (:contentStreamDbIds) AND dh.dimensionspacepointhash = cte.dsp WHERE NOT JSON_CONTAINS_PATH(dh.subtreetags, 'one', :tagPath) @@ -52,20 +52,21 @@ private function addSubtreeTag(ContentStreamDbId $contentStreamDbId, NodeAggrega ) subquery ON h.dimensionspacepointhash = subquery.dsp AND h.childnodeanchor = subquery.id SET h.subtreetags = JSON_INSERT(h.subtreetags, :tagPath, null) - WHERE h.contentstreamdbid = :contentStreamDbId + WHERE h.contentstreamdbid IN (:contentStreamDbIds) SQL; try { $this->dbal->executeStatement($addTagToDescendantsStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'nodeAggregateId' => $nodeAggregateId->value, 'dimensionSpacePointHashes' => $affectedDimensionSpacePoints->getPointHashes(), 'tagPath' => '$."' . $tag->value . '"', ], [ 'dimensionSpacePointHashes' => ArrayParameterType::STRING, + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('1: Failed to add subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamDbId->value, $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716479749, $e); + throw new \RuntimeException(sprintf('1: Failed to add subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716479749, $e); } $addTagToNodeStatement = <<dbal->executeStatement($addTagToNodeStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'nodeAggregateId' => $nodeAggregateId->value, 'dimensionSpacePointHashes' => $affectedDimensionSpacePoints->getPointHashes(), 'tagPath' => '$."' . $tag->value . '"', ], [ 'dimensionSpacePointHashes' => ArrayParameterType::STRING, + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('2: Failed to add subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamDbId->value, $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716479840, $e); + throw new \RuntimeException(sprintf('2: Failed to add subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716479840, $e); } } - private function removeSubtreeTag(ContentStreamDbId $contentStreamDbId, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedDimensionSpacePoints, SubtreeTag $tag): void + private function removeSubtreeTag(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedDimensionSpacePoints, SubtreeTag $tag): void { $removeTagStatement = <<tableNames->hierarchyRelation()} h @@ -102,7 +104,7 @@ private function removeSubtreeTag(ContentStreamDbId $contentStreamDbId, NodeAggr INNER JOIN {$this->tableNames->node()} n ON n.relationanchorpoint = ph.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId - AND ph.contentstreamdbid = :contentStreamDbId + AND ph.contentstreamdbid IN (:contentStreamDbIds) AND ph.dimensionspacepointhash in (:dimensionSpacePointHashes) UNION ALL SELECT @@ -111,7 +113,7 @@ private function removeSubtreeTag(ContentStreamDbId $contentStreamDbId, NodeAggr FROM cte JOIN {$this->tableNames->hierarchyRelation()} dh ON dh.parentnodeanchor = cte.id - AND dh.contentstreamdbid = :contentStreamDbId + AND dh.contentstreamdbid IN (:contentStreamDbIds) AND dh.dimensionspacepointhash = cte.dsp WHERE JSON_EXTRACT(dh.subtreetags, :tagPath) != TRUE @@ -130,27 +132,28 @@ private function removeSubtreeTag(ContentStreamDbId $contentStreamDbId, NodeAggr WHERE ph.parentnodeanchor = gph.childnodeanchor AND n.nodeaggregateid = :nodeAggregateId - AND gph.contentstreamdbid = :contentStreamDbId + AND gph.contentstreamdbid IN (:contentStreamDbIds) LIMIT 1) as containsTagSubQuery ), JSON_SET(subtreetags, :tagPath, null), JSON_REMOVE(subtreetags, :tagPath) ) - WHERE contentstreamdbid = :contentStreamDbId + WHERE contentstreamdbid IN (:contentStreamDbIds) SQL; try { $this->dbal->executeStatement($removeTagStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'nodeAggregateId' => $nodeAggregateId->value, 'dimensionSpacePointHashes' => $affectedDimensionSpacePoints->getPointHashes(), 'tagPath' => '$."' . $tag->value . '"', ], [ 'dimensionSpacePointHashes' => ArrayParameterType::STRING, + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to remove subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamDbId->value, $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716482293, $e); + throw new \RuntimeException(sprintf('Failed to remove subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716482293, $e); } } - private function moveSubtreeTags(ContentStreamDbId $contentStreamDbId, NodeAggregateId $newParentNodeAggregateId, DimensionSpacePoint $coveredDimensionSpacePoint): void + private function moveSubtreeTags(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $newParentNodeAggregateId, DimensionSpacePoint $coveredDimensionSpacePoint): void { $moveSubtreeTagsStatement = <<tableNames->hierarchyRelation()} h, @@ -163,7 +166,7 @@ private function moveSubtreeTags(ContentStreamDbId $contentStreamDbId, NodeAggre INNER JOIN {$this->tableNames->node()} tn ON tn.relationanchorpoint = th.childnodeanchor WHERE tn.nodeaggregateid = :newParentNodeAggregateId - AND th.contentstreamdbid = :contentStreamDbId + AND th.contentstreamdbid IN (:contentStreamDbIds) AND th.dimensionspacepointhash = :dimensionSpacePointHash UNION SELECT @@ -180,7 +183,7 @@ private function moveSubtreeTags(ContentStreamDbId $contentStreamDbId, NodeAggre JOIN {$this->tableNames->hierarchyRelation()} dh ON dh.parentnodeanchor = cte.childnodeanchor - AND dh.contentstreamdbid = :contentStreamDbId + AND dh.contentstreamdbid IN (:contentStreamDbIds) AND dh.dimensionspacepointhash = :dimensionSpacePointHash ) SELECT * FROM cte @@ -196,23 +199,25 @@ private function moveSubtreeTags(ContentStreamDbId $contentStreamDbId, NodeAggre ) WHERE h.childnodeanchor = r.childnodeanchor - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { // Mysql hack, too eager to optimize https://dev.mysql.com/doc/refman/8.4/en/derived-table-optimization.html $this->dbal->executeQuery('set optimizer_switch="derived_merge=off"'); $this->dbal->executeStatement($moveSubtreeTagsStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'newParentNodeAggregateId' => $newParentNodeAggregateId->value, 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to move subtree tags for content stream %s, new parent node aggregate id %s and dimension space point %s: %s', $contentStreamDbId->value, $newParentNodeAggregateId->value, $coveredDimensionSpacePoint->toJson(), $e->getMessage()), 1716482574, $e); + throw new \RuntimeException(sprintf('Failed to move subtree tags for content stream %s, new parent node aggregate id %s and dimension space point %s: %s', $contentStreamDbIds->toDebugString(), $newParentNodeAggregateId->value, $coveredDimensionSpacePoint->toJson(), $e->getMessage()), 1716482574, $e); } } - private function subtreeTagsForHierarchyRelation(ContentStreamDbId $contentStreamDbId, NodeRelationAnchorPoint $parentNodeAnchorPoint, DimensionSpacePoint $dimensionSpacePoint): NodeTags + private function subtreeTagsForHierarchyRelation(ContentStreamDbIds $contentStreamDbIds, NodeRelationAnchorPoint $parentNodeAnchorPoint, DimensionSpacePoint $dimensionSpacePoint): NodeTags { if ($parentNodeAnchorPoint->equals(NodeRelationAnchorPoint::forRootEdge())) { return NodeTags::createEmpty(); @@ -222,18 +227,20 @@ private function subtreeTagsForHierarchyRelation(ContentStreamDbId $contentStrea SELECT h.subtreetags FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :parentNodeAnchorPoint - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash = :dimensionSpacePointHash ', [ 'parentNodeAnchorPoint' => $parentNodeAnchorPoint->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash, + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to fetch subtree tags for hierarchy parent anchor point "%s" in content subgraph "%s@%s": %s', $parentNodeAnchorPoint->value, $dimensionSpacePoint->toJson(), $contentStreamDbId->value, $e->getMessage()), 1716478760, $e); + throw new \RuntimeException(sprintf('Failed to fetch subtree tags for hierarchy parent anchor point "%s" in content subgraph "%s@%s": %s', $parentNodeAnchorPoint->value, $dimensionSpacePoint->toJson(), $contentStreamDbIds->toDebugString(), $e->getMessage()), 1716478760, $e); } if (!is_string($subtreeTagsJson)) { - throw new \RuntimeException(sprintf('Failed to fetch subtree tags for hierarchy parent anchor point "%s" in content subgraph "%s@%s"', $parentNodeAnchorPoint->value, $dimensionSpacePoint->toJson(), $contentStreamDbId->value), 1704199847); + throw new \RuntimeException(sprintf('Failed to fetch subtree tags for hierarchy parent anchor point "%s" in content subgraph "%s@%s"', $parentNodeAnchorPoint->value, $dimensionSpacePoint->toJson(), $contentStreamDbIds->value), 1704199847); } return NodeFactory::extractNodeTagsFromJson($subtreeTagsJson); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 20d555ac8dd..92e7fc940bd 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -19,7 +19,7 @@ use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Query\QueryBuilder; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; @@ -76,7 +76,7 @@ public function __construct( private readonly ContentGraphTableNames $tableNames, public readonly WorkspaceName $workspaceName, public readonly ContentStreamId $contentStreamId, - public readonly ContentStreamDbId $contentStreamDbId, + public readonly ContentStreamDbIds $contentStreamDbIds, ) { $this->nodeQueryBuilder = new NodeQueryBuilder($this->dbal, $this->tableNames); } @@ -98,7 +98,7 @@ public function getSubgraph( return new ContentSubgraph( $this->contentRepositoryId, $this->workspaceName, - $this->contentStreamDbId, + $this->contentStreamDbIds, $dimensionSpacePoint, $visibilityConstraints, $this->dbal, @@ -137,7 +137,7 @@ public function findRootNodeAggregateByType( public function findRootNodeAggregates( FindRootNodeAggregatesFilter $filter, ): NodeAggregates { - $rootNodeAggregateQueryBuilder = $this->nodeQueryBuilder->buildFindRootNodeAggregatesQuery($this->contentStreamDbId, $filter); + $rootNodeAggregateQueryBuilder = $this->nodeQueryBuilder->buildFindRootNodeAggregatesQuery($this->contentStreamDbIds, $filter); return $this->mapQueryBuilderToNodeAggregates($rootNodeAggregateQueryBuilder); } @@ -148,8 +148,10 @@ public function findNodeAggregatesByType( $queryBuilder ->andWhere('n.nodetypename = :nodeTypeName') ->setParameters([ - 'contentStreamDbId' => $this->contentStreamDbId->value, + 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray(), 'nodeTypeName' => $nodeTypeName->value, + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } @@ -162,7 +164,9 @@ public function findNodeAggregateById( ->orderBy('n.relationanchorpoint', 'DESC') ->setParameters([ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbId' => $this->contentStreamDbId->value + 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray() + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER ]); return $this->nodeFactory->mapNodeRowsToNodeAggregate( @@ -180,9 +184,10 @@ public function findNodeAggregatesByIds( ->orderBy('n.relationanchorpoint', 'DESC') ->setParameters([ 'nodeAggregateIds' => $nodeAggregateIds->toStringArray(), - 'contentStreamDbId' => $this->contentStreamDbId->value + 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray() ], [ - 'nodeAggregateIds' => ArrayParameterType::STRING + 'nodeAggregateIds' => ArrayParameterType::STRING, + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); @@ -199,11 +204,13 @@ public function findParentNodeAggregates( $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') - ->andWhere('ch.contentstreamdbid = :contentStreamDbId') + ->andWhere('ch.contentstreamdbid = :contentStreamDbIds') ->andWhere('cn.nodeaggregateid = :nodeAggregateId') ->setParameters([ 'nodeAggregateId' => $childNodeAggregateId->value, - 'contentStreamDbId' => $this->contentStreamDbId->value + 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray() + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); @@ -215,20 +222,20 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate ->select('ch.parentnodeanchor') ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') - ->where('ch.contentstreamdbid = :contentStreamDbId') + ->where('ch.contentstreamdbid = :contentStreamDbIds') ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); $queryBuilderRecursive = $this->createQueryBuilder() ->select('ph.parentnodeanchor') ->from('ancestry', 'ch') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = ch.parentnodeanchor') - ->where('ph.contentstreamdbid = :contentStreamDbId'); + ->where('ph.contentstreamdbid = :contentStreamDbIds'); $queryBuilderCte = $this->createQueryBuilder() ->select('n.nodeAggregateId') ->from('ancestry', 'a') ->innerJoin('a', $this->nodeQueryBuilder->tableNames->node(), 'n', 'n.relationanchorpoint = a.parentnodeanchor') - ->setParameter('contentStreamDbId', $this->contentStreamDbId->value) + ->setParameter('contentStreamDbIds', $this->contentStreamDbIds->toIntArray(), ArrayParameterType::STRING) ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); $nodeAggregateIdRows = $this->fetchCteResults( @@ -244,7 +251,7 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate public function findChildNodeAggregates( NodeAggregateId $parentNodeAggregateId ): NodeAggregates { - $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamDbId); + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamDbIds); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } @@ -255,7 +262,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr ->from($this->nodeQueryBuilder->tableNames->node(), 'pn') ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') - ->where('ch.contentstreamdbid = :contentStreamDbId') + ->where('ch.contentstreamdbid = :contentStreamDbIds') ->andWhere('ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash') ->andWhere('cn.nodeaggregateid = :childNodeAggregateId') ->andWhere('cn.origindimensionspacepointhash = :childOriginDimensionSpacePointHash'); @@ -266,11 +273,13 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') - ->andWhere('h.contentstreamdbid = :contentStreamDbId') + ->andWhere('h.contentstreamdbid = :contentStreamDbIds') ->setParameters([ - 'contentStreamDbId' => $this->contentStreamDbId->value, + 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray(), 'childNodeAggregateId' => $childNodeAggregateId->value, 'childOriginDimensionSpacePointHash' => $childOriginDimensionSpacePoint->hash, + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); return $this->nodeFactory->mapNodeRowsToNodeAggregate( @@ -282,7 +291,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr public function findTetheredChildNodeAggregates(NodeAggregateId $parentNodeAggregateId): NodeAggregates { - $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamDbId) + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamDbIds) ->andWhere('cn.classification = :tetheredClassification') ->setParameter('tetheredClassification', NodeAggregateClassification::CLASSIFICATION_TETHERED->value); @@ -293,7 +302,7 @@ public function findChildNodeAggregateByName( NodeAggregateId $parentNodeAggregateId, NodeName $name ): ?NodeAggregate { - $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamDbId) + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamDbIds) ->andWhere('cn.name = :relationName') ->setParameter('relationName', $name->value); @@ -310,18 +319,19 @@ public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeNam ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') - ->andWhere('ph.contentstreamdbid = :contentStreamDbId') - ->andWhere('h.contentstreamdbid = :contentStreamDbId') + ->andWhere('ph.contentstreamdbid = :contentStreamDbIds') + ->andWhere('h.contentstreamdbid = :contentStreamDbIds') ->andWhere('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)') ->andWhere('n.name = :nodeName') ->setParameters([ 'parentNodeAggregateId' => $parentNodeAggregateId->value, 'parentNodeOriginDimensionSpacePointHash' => $parentNodeOriginDimensionSpacePoint->hash, - 'contentStreamDbId' => $this->contentStreamDbId->value, + 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray(), 'dimensionSpacePointHashes' => $dimensionSpacePointsToCheck->getPointHashes(), 'nodeName' => $nodeName->value ], [ 'dimensionSpacePointHashes' => ArrayParameterType::STRING, + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); $dimensionSpacePoints = []; foreach ($this->fetchRows($queryBuilder) as $hierarchyRelationData) { @@ -340,13 +350,15 @@ public function findNodeAggregatesTaggedBy(SubtreeTag $subtreeTag): NodeAggregat ->innerJoin('th', $this->tableNames->hierarchyRelation(), 'h', 'th.childnodeanchor = h.childnodeanchor') ->innerJoin('h', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->where('th.contentstreamdbid = :contentStreamDbId') + ->where('th.contentstreamdbid = :contentStreamDbIds') ->andWhere('JSON_EXTRACT(th.subtreetags, :tagPath) LIKE "true"') - ->andWhere('h.contentstreamdbid = :contentStreamDbId') + ->andWhere('h.contentstreamdbid = :contentStreamDbIds') ->orderBy('n.relationanchorpoint', 'DESC') ->setParameters([ 'tagPath' => '$."' . $subtreeTag->value . '"', - 'contentStreamDbId' => $this->contentStreamDbId->value + 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray() + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php index b6f25ecc716..7ffe0b20050 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php @@ -17,7 +17,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception as DBALException; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** @@ -29,7 +29,7 @@ final class ContentStreamDbIdFinder { /** - * @var array + * @var array */ private array $contentStreamIdRuntimeCache = []; @@ -39,22 +39,23 @@ public function __construct( ) { } - public function getContentStreamDbId(ContentStreamId $contentStreamId): ContentStreamDbId + // todo rename + public function getContentStreamDbId(ContentStreamId $contentStreamId): ContentStreamDbIds { - $contentStreamDbId = $this->getFromRuntimeCache($contentStreamId); - if ($contentStreamDbId === null) { + $contentStreamDbIds = $this->getFromRuntimeCache($contentStreamId); + if ($contentStreamDbIds === null) { $this->fillRuntimeCacheFromDatabase(); - $contentStreamDbId = $this->getFromRuntimeCache($contentStreamId); + $contentStreamDbIds = $this->getFromRuntimeCache($contentStreamId); } - if ($contentStreamDbId === null) { + if ($contentStreamDbIds === null) { throw new \RuntimeException(sprintf('A ContentStream with id "%s" was not found in the projection, cannot determine ContentStreamDbId.', $contentStreamId->value), 1769945094); } - return $contentStreamDbId; + return $contentStreamDbIds; } - private function getFromRuntimeCache(ContentStreamId $contentStreamId): ?ContentStreamDbId + private function getFromRuntimeCache(ContentStreamId $contentStreamId): ?ContentStreamDbIds { return $this->contentStreamIdRuntimeCache[$contentStreamId->value] ?? null; } @@ -62,15 +63,19 @@ private function getFromRuntimeCache(ContentStreamId $contentStreamId): ?Content private function fillRuntimeCacheFromDatabase(): void { $allContentStreamIdsStatement = <<tableNames->contentStream()} + SELECT dbId, id FROM {$this->tableNames->contentStreamId()} SQL; try { $allContentStreamIds = $this->dbal->fetchAllAssociative($allContentStreamIdsStatement); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load content stream ids from database: %s', $e->getMessage()), 1769945050, $e); } + $ids = []; foreach ($allContentStreamIds as $contentStreamIdRow) { - $this->contentStreamIdRuntimeCache[(string)$contentStreamIdRow['id']] = ContentStreamDbId::fromInt($contentStreamIdRow['dbId']); + $ids[$contentStreamIdRow['id']][] = $contentStreamIdRow['dbId']; + } + foreach ($ids as $contentStreamId => $dbIds) { + $this->contentStreamIdRuntimeCache[$contentStreamId] = ContentStreamDbIds::fromArray($dbIds); } } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index f65c9dd553e..504adb8203d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -20,7 +20,7 @@ use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; @@ -95,7 +95,7 @@ final class ContentSubgraph implements ContentSubgraphInterface public function __construct( private readonly ContentRepositoryId $contentRepositoryId, private readonly WorkspaceName $workspaceName, - private readonly ContentStreamDbId $contentStreamDbId, + private readonly ContentStreamDbIds $contentStreamDbIds, private readonly DimensionSpacePoint $dimensionSpacePoint, private readonly VisibilityConstraints $visibilityConstraints, private readonly Connection $dbal, @@ -169,7 +169,7 @@ public function countBackReferences(NodeAggregateId $nodeAggregateId, CountBackR public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbId, $this->dimensionSpacePoint) + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbIds, $this->dimensionSpacePoint) ->andWhere('n.nodeaggregateid = :nodeAggregateId') ->setParameter('nodeAggregateId', $nodeAggregateId->value); $this->addSubtreeTagConstraints($queryBuilder); @@ -178,7 +178,7 @@ public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node public function findNodesByIds(NodeAggregateIds $nodeAggregateIds): Nodes { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbId, $this->dimensionSpacePoint) + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbIds, $this->dimensionSpacePoint) ->andWhere('n.nodeaggregateid in (:nodeAggregateIds)') ->setParameter('nodeAggregateIds', $nodeAggregateIds->toStringArray(), ArrayParameterType::STRING); $this->addSubtreeTagConstraints($queryBuilder); @@ -187,7 +187,7 @@ public function findNodesByIds(NodeAggregateIds $nodeAggregateIds): Nodes public function findRootNodeByType(NodeTypeName $nodeTypeName): ?Node { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbId, $this->dimensionSpacePoint) + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbIds, $this->dimensionSpacePoint) ->andWhere('n.nodetypename = :nodeTypeName')->setParameter('nodeTypeName', $nodeTypeName->value) ->andWhere('n.classification = :nodeAggregateClassification')->setParameter('nodeAggregateClassification', NodeAggregateClassification::CLASSIFICATION_ROOT->value); $this->addSubtreeTagConstraints($queryBuilder); @@ -196,7 +196,7 @@ public function findRootNodeByType(NodeTypeName $nodeTypeName): ?Node public function findParentNode(NodeAggregateId $childNodeAggregateId): ?Node { - $queryBuilder = $this->nodeQueryBuilder->buildBasicParentNodeQuery($childNodeAggregateId, $this->contentStreamDbId, $this->dimensionSpacePoint); + $queryBuilder = $this->nodeQueryBuilder->buildBasicParentNodeQuery($childNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint); $this->addSubtreeTagConstraints($queryBuilder, 'ph'); return $this->fetchNode($queryBuilder); } @@ -228,7 +228,7 @@ public function findNodeByAbsolutePath(AbsoluteNodePath $path): ?Node */ private function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNodeAggregateId, NodeName $nodeName): ?Node { - $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamDbId, $this->dimensionSpacePoint) + $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint) ->andWhere('n.name = :edgeName')->setParameter('edgeName', $nodeName->value); $this->addSubtreeTagConstraints($queryBuilder); return $this->fetchNode($queryBuilder); @@ -276,7 +276,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi ->select('n.*, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('h.contentstreamdbid = :contentStreamDbId') + ->where('h.contentstreamdbid IN (:contentStreamDbIds)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial); @@ -286,7 +286,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi ->from('tree', 'p') ->innerJoin('p', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') ->innerJoin('p', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = h.childnodeanchor') - ->where('h.contentstreamdbid = :contentStreamDbId') + ->where('h.contentstreamdbid IN (:contentStreamDbIds)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); if ($filter->maximumLevels !== null) { $queryBuilderRecursive->andWhere('p.level < :maximumLevels')->setParameter('maximumLevels', $filter->maximumLevels); @@ -301,7 +301,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi ->from('tree') ->orderBy('level') ->addOrderBy('position') - ->setParameter('contentStreamDbId', $this->contentStreamDbId->value) + ->setParameter('contentStreamDbIds', $this->contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) ->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); @@ -380,7 +380,7 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose ->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(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') - ->andWhere('ph.contentstreamdbid = :contentStreamDbId') + ->andWhere('ph.contentstreamdbid IN (:contentStreamDbIds)') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial, 'ph'); @@ -390,11 +390,11 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose ->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.contentstreamdbid = :contentStreamDbId') + ->where('h.contentstreamdbid IN (:contentStreamDbIds)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamDbId, $this->dimensionSpacePoint); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint); $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'pn'); $nodeRows = $this->fetchCteResults( $queryBuilderInitial, @@ -437,7 +437,7 @@ public function countDescendantNodes(NodeAggregateId $entryNodeAggregateId, Coun public function countNodes(): int { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbId, $this->dimensionSpacePoint, 'n', 'COUNT(*)'); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbIds, $this->dimensionSpacePoint, 'n', 'COUNT(*)'); try { $result = $this->executeQuery($queryBuilder)->fetchOne(); } catch (DBALException $e) { @@ -483,7 +483,7 @@ private function addSubtreeTagConstraints(QueryBuilder $queryBuilder, string $hi private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, FindChildNodesFilter|CountChildNodesFilter $filter): QueryBuilder { - $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamDbId, $this->dimensionSpacePoint); + $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); } @@ -501,9 +501,12 @@ private function buildReferencesQuery(NodeAggregateId $nodeAggregateId, FindRefe { $subselectParameters = [ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbId' => $this->contentStreamDbId->value, + 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray(), 'dimensionSpacePointHash' => $this->dimensionSpacePoint->hash, ]; + $subselectTypes = [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, + ]; $subtreeTagConstraints = ''; $i = 0; foreach ($this->visibilityConstraints->excludedSubtreeTags as $excludedTag) { @@ -521,12 +524,12 @@ private function buildReferencesQuery(NodeAggregateId $nodeAggregateId, FindRefe SELECT relationanchorpoint FROM ' . $this->nodeQueryBuilder->tableNames->node() . ' sn JOIN ' . $this->nodeQueryBuilder->tableNames->hierarchyRelation() . ' sh ON sn.relationanchorpoint = sh.childnodeanchor WHERE sn.nodeaggregateid = :nodeAggregateId - AND sh.contentstreamdbid = :contentStreamDbId + AND sh.contentstreamdbid IN (:contentStreamDbIds) AND sh.dimensionspacepointhash = :dimensionSpacePointHash ' . $subtreeTagConstraints . ' - )')->setParameters($subselectParameters) + )')->setParameters($subselectParameters, $subselectTypes) ->andWhere('dh.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('dh.contentstreamdbid = :contentStreamDbId')->setParameter('contentStreamDbId', $this->contentStreamDbId->value); + ->andWhere('dh.contentstreamdbid IN (:contentStreamDbIds)')->setParameter('contentStreamDbIds', $this->contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER); $this->addSubtreeTagConstraints($queryBuilder, 'dh'); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), "dn"); @@ -565,9 +568,12 @@ private function buildBackreferencesQuery(NodeAggregateId $nodeAggregateId, Find { $subselectParameters = [ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbId' => $this->contentStreamDbId->value, + 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray(), 'dimensionSpacePointHash' => $this->dimensionSpacePoint->hash, ]; + $subselectTypes = [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, + ]; $subtreeTagConstraints = ''; $i = 0; foreach ($this->visibilityConstraints->excludedSubtreeTags as $excludedTag) { @@ -585,12 +591,12 @@ private function buildBackreferencesQuery(NodeAggregateId $nodeAggregateId, Find SELECT nodeaggregateid FROM ' . $this->nodeQueryBuilder->tableNames->node() . ' dn JOIN ' . $this->nodeQueryBuilder->tableNames->hierarchyRelation() . ' dh ON dn.relationanchorpoint = dh.childnodeanchor WHERE dn.nodeaggregateid = :nodeAggregateId - AND dh.contentstreamdbid = :contentStreamDbId + AND dh.contentstreamdbid IN (:contentStreamDbIds) AND dh.dimensionspacepointhash = :dimensionSpacePointHash ' . $subtreeTagConstraints . ' - )')->setParameters($subselectParameters) + )')->setParameters($subselectParameters, $subselectTypes) ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('sh.contentstreamdbid = :contentStreamDbId')->setParameter('contentStreamDbId', $this->contentStreamDbId->value); + ->andWhere('sh.contentstreamdbid IN (:contentStreamDbIds)')->setParameter('contentStreamDbIds', $this->contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER); $this->addSubtreeTagConstraints($queryBuilder, 'sh'); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), "sn"); @@ -627,7 +633,7 @@ private function buildBackreferencesQuery(NodeAggregateId $nodeAggregateId, Find private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, FindPrecedingSiblingNodesFilter|FindSucceedingSiblingNodesFilter $filter): QueryBuilder { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeSiblingsQuery($preceding, $siblingNodeAggregateId, $this->contentStreamDbId, $this->dimensionSpacePoint); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeSiblingsQuery($preceding, $siblingNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint); $this->addSubtreeTagConstraints($queryBuilder); if ($filter->nodeTypes !== null) { @@ -657,9 +663,9 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId ->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.contentstreamdbid = :contentStreamDbId') + ->where('ch.contentstreamdbid IN (:contentStreamDbIds)') ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash') - ->andWhere('ph.contentstreamdbid = :contentStreamDbId') + ->andWhere('ph.contentstreamdbid IN (:contentStreamDbIds)') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial, 'ph'); @@ -670,11 +676,11 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId ->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.contentstreamdbid = :contentStreamDbId') + ->where('h.contentstreamdbid IN (:contentStreamDbIds)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamDbId, $this->dimensionSpacePoint); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'pn'); } @@ -694,9 +700,9 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('n', $this->nodeQueryBuilder->tableNames->node(), 'p', 'p.relationanchorpoint = h.parentnodeanchor') ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') - ->where('h.contentstreamdbid = :contentStreamDbId') + ->where('h.contentstreamdbid IN (:contentStreamDbIds)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') - ->andWhere('ph.contentstreamdbid = :contentStreamDbId') + ->andWhere('ph.contentstreamdbid IN (:contentStreamDbIds)') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('p.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial); @@ -706,11 +712,11 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate ->from('tree', 'pn') ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') - ->where('h.contentstreamdbid = :contentStreamDbId') + ->where('h.contentstreamdbid IN (:contentStreamDbIds)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamDbId, $this->dimensionSpacePoint, 'tree', 'n'); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint, 'tree', 'n'); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index d02b04ed3be..d781800a018 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -20,6 +20,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; @@ -51,7 +52,7 @@ public function __construct( * correct) */ public function findParentNode( - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $originDimensionSpacePoint, ?DimensionSpacePoint $coveredDimensionSpacePoint = null @@ -68,27 +69,29 @@ public function findParentNode( WHERE c.nodeaggregateid = :childNodeAggregateId AND c.origindimensionspacepointhash = :originDimensionSpacePointHash - AND ph.contentstreamdbid = :contentStreamDbId - AND ch.contentstreamdbid = :contentStreamDbId + AND ph.contentstreamdbid IN (:contentStreamDbIds) + AND ch.contentstreamdbid IN (:contentStreamDbIds) AND ph.dimensionspacepointhash = :coveredDimensionSpacePointHash AND ch.dimensionspacepointhash = :coveredDimensionSpacePointHash SQL; try { $nodeRow = $this->dbal->fetchAssociative($parentNodeStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'childNodeAggregateId' => $childNodeAggregateId->value, 'originDimensionSpacePointHash' => $originDimensionSpacePoint->hash, 'coveredDimensionSpacePointHash' => $coveredDimensionSpacePoint->hash ?? $originDimensionSpacePoint->hash + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load parent node for content stream %s, child node aggregate id %s, origin dimension space point %s from database: %s', $contentStreamDbId->value, $childNodeAggregateId->value, $originDimensionSpacePoint->toJson(), $e->getMessage()), 1716475976, $e); + throw new \RuntimeException(sprintf('Failed to load parent node for content stream %s, child node aggregate id %s, origin dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $childNodeAggregateId->value, $originDimensionSpacePoint->toJson(), $e->getMessage()), 1716475976, $e); } return $nodeRow ? NodeRecord::fromDatabaseRow($nodeRow) : null; } public function findNodeInAggregate( - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, DimensionSpacePoint $coveredDimensionSpacePoint ): ?NodeRecord { @@ -101,17 +104,19 @@ public function findNodeInAggregate( INNER JOIN {$this->tableNames->dimensionSpacePoints()} dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $nodeRow = $this->dbal->fetchAssociative($nodeInAggregateStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'nodeAggregateId' => $nodeAggregateId->value, 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load node for content stream %s, aggregate id %s and covered dimension space point %s from database: %s', $contentStreamDbId->value, $nodeAggregateId->value, $coveredDimensionSpacePoint->toJson(), $e->getMessage()), 1716474165, $e); + throw new \RuntimeException(sprintf('Failed to load node for content stream %s, aggregate id %s and covered dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $coveredDimensionSpacePoint->toJson(), $e->getMessage()), 1716474165, $e); } return $nodeRow ? NodeRecord::fromDatabaseRow($nodeRow) : null; @@ -120,7 +125,7 @@ public function findNodeInAggregate( public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStream( NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $originDimensionSpacePoint, - ContentStreamDbId $contentStreamDbId + ContentStreamDbIds $contentStreamDbIds ): ?NodeRelationAnchorPoint { $relationAnchorPointsStatement = <<dbal->fetchFirstColumn($relationAnchorPointsStatement, [ 'nodeAggregateId' => $nodeAggregateId->value, 'originDimensionSpacePointHash' => $originDimensionSpacePoint->hash, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load node anchor points for content stream %s, node aggregate %s and origin dimension space point %s from database: %s', $contentStreamDbId->value, $nodeAggregateId->value, $originDimensionSpacePoint->toJson(), $e->getMessage()), 1716474224, $e); + throw new \RuntimeException(sprintf('Failed to load node anchor points for content stream %s, node aggregate %s and origin dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $originDimensionSpacePoint->toJson(), $e->getMessage()), 1716474224, $e); } if (count($relationAnchorPoints) > 1) { - throw new \RuntimeException(sprintf('More than one node anchor point for content stream: %s, node aggregate id: %s and origin dimension space point: %s – this should not happen and might be a conceptual problem!', $contentStreamDbId->value, $nodeAggregateId->value, $originDimensionSpacePoint->toJson()), 1716474484); + throw new \RuntimeException(sprintf('More than one node anchor point for content stream: %s, node aggregate id: %s and origin dimension space point: %s – this should not happen and might be a conceptual problem!', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $originDimensionSpacePoint->toJson()), 1716474484); } return $relationAnchorPoints === [] ? null : NodeRelationAnchorPoint::fromInteger($relationAnchorPoints[0]); } @@ -154,7 +161,7 @@ public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStrea */ public function getAnchorPointsForNodeAggregateInContentStream( NodeAggregateId $nodeAggregateId, - ContentStreamDbId $contentStreamDbId + ContentStreamDbIds $contentStreamDbIds ): iterable { $relationAnchorPointsStatement = <<tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) SQL; try { $relationAnchorPoints = $this->dbal->fetchFirstColumn($relationAnchorPointsStatement, [ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load node anchor points for content stream %s and node aggregate id %s from database: %s', $contentStreamDbId->value, $nodeAggregateId->value, $e->getMessage()), 1716474706, $e); + throw new \RuntimeException(sprintf('Failed to load node anchor points for content stream %s and node aggregate id %s from database: %s', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $e->getMessage()), 1716474706, $e); } return array_map(NodeRelationAnchorPoint::fromInteger(...), $relationAnchorPoints); @@ -204,7 +213,7 @@ public function determineHierarchyRelationPosition( ?NodeRelationAnchorPoint $parentAnchorPoint, ?NodeRelationAnchorPoint $childAnchorPoint, ?NodeRelationAnchorPoint $succeedingSiblingAnchorPoint, - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint ): int { if (!$parentAnchorPoint && !$childAnchorPoint) { @@ -221,18 +230,20 @@ public function determineHierarchyRelationPosition( {$this->tableNames->hierarchyRelation()} h WHERE h.childnodeanchor = :succeedingSiblingAnchorPoint - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { /** @var array $succeedingSiblingRelation */ $succeedingSiblingRelation = $this->dbal->fetchAssociative($succeedingSiblingRelationStatement, [ 'succeedingSiblingAnchorPoint' => $succeedingSiblingAnchorPoint->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - 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', $contentStreamDbId->value, $succeedingSiblingAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716474854, $e); + 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', $contentStreamDbIds->toDebugString(), $succeedingSiblingAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716474854, $e); } if (!$succeedingSiblingRelation) { @@ -252,19 +263,21 @@ public function determineHierarchyRelationPosition( {$this->tableNames->hierarchyRelation()} h WHERE h.parentnodeanchor = :anchorPoint - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash = :dimensionSpacePointHash AND h.position < :position SQL; try { $precedingSiblingData = $this->dbal->fetchAssociative($precedingSiblingStatement, [ 'anchorPoint' => $parentAnchorPoint->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash, 'position' => $succeedingSiblingPosition + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load preceding sibling relations for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamDbId->value, $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716474957, $e); + throw new \RuntimeException(sprintf('Failed to load preceding sibling relations for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716474957, $e); } $precedingSiblingPosition = $precedingSiblingData ? ($precedingSiblingData['position'] ?? null) : null; if (!is_null($precedingSiblingPosition)) { @@ -285,18 +298,20 @@ public function determineHierarchyRelationPosition( {$this->tableNames->hierarchyRelation()} h WHERE h.childnodeanchor = :childAnchorPoint - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { /** @var array $childHierarchyRelationData */ $childHierarchyRelationData = $this->dbal->fetchAssociative($childHierarchyRelationStatement, [ 'childAnchorPoint' => $childAnchorPoint->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load child hierarchy relation for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamDbId->value, $childAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475001, $e); + throw new \RuntimeException(sprintf('Failed to load child hierarchy relation for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $childAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475001, $e); } $parentAnchorPoint = NodeRelationAnchorPoint::fromInteger( $childHierarchyRelationData['parentnodeanchor'] @@ -309,17 +324,19 @@ public function determineHierarchyRelationPosition( {$this->tableNames->hierarchyRelation()} h WHERE h.parentnodeanchor = :parentAnchorPoint - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $rightmostSucceedingSiblingRelationData = $this->dbal->fetchAssociative($rightmostSucceedingSiblingRelationStatement, [ 'parentAnchorPoint' => $parentAnchorPoint->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to right most succeeding relation for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamDbId->value, $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475046, $e); + throw new \RuntimeException(sprintf('Failed to right most succeeding relation for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475046, $e); } if ($rightmostSucceedingSiblingRelationData) { @@ -338,7 +355,7 @@ public function determineHierarchyRelationPosition( */ public function getOutgoingHierarchyRelationsForNodeAndSubgraph( NodeRelationAnchorPoint $parentAnchorPoint, - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint ): array { $outgoingHierarchyRelationsStatement = <<tableNames->hierarchyRelation()} h WHERE h.parentnodeanchor = :parentAnchorPoint - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $rows = $this->dbal->fetchAllAssociative($outgoingHierarchyRelationsStatement, [ 'parentAnchorPoint' => $parentAnchorPoint->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, parent anchor point %s and dimension space point %s from database: %s', $contentStreamDbId->value, $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475151, $e); + throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, parent anchor point %s and dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475151, $e); } return array_map($this->mapRawDataToHierarchyRelation(...), $rows); } @@ -368,7 +387,7 @@ public function getOutgoingHierarchyRelationsForNodeAndSubgraph( */ public function getIngoingHierarchyRelationsForNodeAndSubgraph( NodeRelationAnchorPoint $childAnchorPoint, - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint ): array { $ingoingHierarchyRelationsStatement = <<tableNames->hierarchyRelation()} h WHERE h.childnodeanchor = :childAnchorPoint - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $rows = $this->dbal->fetchAllAssociative($ingoingHierarchyRelationsStatement, [ 'childAnchorPoint' => $childAnchorPoint->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, child anchor point %s and dimension space point %s from database: %s', $contentStreamDbId->value, $childAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475151, $e); + throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, child anchor point %s and dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $childAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475151, $e); } return array_map($this->mapRawDataToHierarchyRelation(...), $rows); } @@ -398,7 +419,7 @@ public function getIngoingHierarchyRelationsForNodeAndSubgraph( */ public function findIngoingHierarchyRelationsForNode( NodeRelationAnchorPoint $childAnchorPoint, - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, ?DimensionSpacePointSet $restrictToSet = null ): array { $ingoingHierarchyRelationsStatement = <<tableNames->hierarchyRelation()} h WHERE h.childnodeanchor = :childAnchorPoint - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) SQL; $parameters = [ 'childAnchorPoint' => $childAnchorPoint->value, - 'contentStreamDbId' => $contentStreamDbId->value + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray() + ]; + $types = [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]; - $types = []; if ($restrictToSet) { $ingoingHierarchyRelationsStatement .= ' AND h.dimensionspacepointhash IN (:dimensionSpacePointHashes)'; @@ -424,7 +447,7 @@ public function findIngoingHierarchyRelationsForNode( try { $rows = $this->dbal->fetchAllAssociative($ingoingHierarchyRelationsStatement, $parameters, $types); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, child anchor point %s and dimension space points %s from database: %s', $contentStreamDbId->value, $childAnchorPoint->value, $restrictToSet?->toJson() ?? '[any]', $e->getMessage()), 1716476299, $e); + throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, child anchor point %s and dimension space points %s from database: %s', $contentStreamDbIds->toDebugString(), $childAnchorPoint->value, $restrictToSet?->toJson() ?? '[any]', $e->getMessage()), 1716476299, $e); } $relations = []; foreach ($rows as $row) { @@ -438,7 +461,7 @@ public function findIngoingHierarchyRelationsForNode( */ public function findOutgoingHierarchyRelationsForNode( NodeRelationAnchorPoint $parentAnchorPoint, - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, ?DimensionSpacePointSet $restrictToSet = null ): array { $outgoingHierarchyRelationsStatement = <<tableNames->hierarchyRelation()} h WHERE h.parentnodeanchor = :parentAnchorPoint - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) SQL; $parameters = [ 'parentAnchorPoint' => $parentAnchorPoint->value, - 'contentStreamDbId' => $contentStreamDbId->value + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray() + ]; + $types = [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]; - $types = []; if ($restrictToSet) { $outgoingHierarchyRelationsStatement .= ' AND h.dimensionspacepointhash IN (:dimensionSpacePointHashes)'; @@ -464,7 +489,7 @@ public function findOutgoingHierarchyRelationsForNode( try { $rows = $this->dbal->fetchAllAssociative($outgoingHierarchyRelationsStatement, $parameters, $types); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, parent anchor point %s and dimension space points %s from database: %s', $contentStreamDbId->value, $parentAnchorPoint->value, $restrictToSet?->toJson() ?? '[any]', $e->getMessage()), 1716476573, $e); + throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, parent anchor point %s and dimension space points %s from database: %s', $contentStreamDbIds->toDebugString(), $parentAnchorPoint->value, $restrictToSet?->toJson() ?? '[any]', $e->getMessage()), 1716476573, $e); } $relations = []; foreach ($rows as $row) { @@ -477,7 +502,7 @@ public function findOutgoingHierarchyRelationsForNode( * @return array */ public function findOutgoingHierarchyRelationsForNodeAggregate( - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $dimensionSpacePointSet ): array { @@ -489,19 +514,20 @@ public function findOutgoingHierarchyRelationsForNodeAggregate( INNER JOIN {$this->tableNames->node()} n ON h.parentnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) AND h.dimensionspacepointhash IN (:dimensionSpacePointHashes) SQL; try { $rows = $this->dbal->fetchAllAssociative($outgoingHierarchyRelationsStatement, [ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'dimensionSpacePointHashes' => $dimensionSpacePointSet->getPointHashes() ], [ - 'dimensionSpacePointHashes' => ArrayParameterType::STRING + 'dimensionSpacePointHashes' => ArrayParameterType::STRING, + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, node aggregate id %s and dimension space points %s from database: %s', $contentStreamDbId->value, $nodeAggregateId->value, $dimensionSpacePointSet->toJson(), $e->getMessage()), 1716476690, $e); + throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, node aggregate id %s and dimension space points %s from database: %s', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $dimensionSpacePointSet->toJson(), $e->getMessage()), 1716476690, $e); } return array_map($this->mapRawDataToHierarchyRelation(...), $rows); } @@ -510,7 +536,7 @@ public function findOutgoingHierarchyRelationsForNodeAggregate( * @return array */ public function findIngoingHierarchyRelationsForNodeAggregate( - ContentStreamDbId $contentStreamDbId, + ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, ?DimensionSpacePointSet $dimensionSpacePointSet = null ): array { @@ -522,13 +548,15 @@ public function findIngoingHierarchyRelationsForNodeAggregate( INNER JOIN {$this->tableNames->node()} n ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamdbid = :contentStreamDbId + AND h.contentstreamdbid IN (:contentStreamDbIds) SQL; $parameters = [ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + ]; + $types = [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, ]; - $types = []; if ($dimensionSpacePointSet !== null) { $ingoingHierarchyRelationsStatement .= ' AND h.dimensionspacepointhash IN (:dimensionSpacePointHashes)'; $parameters['dimensionSpacePointHashes'] = $dimensionSpacePointSet->getPointHashes(); @@ -537,7 +565,7 @@ public function findIngoingHierarchyRelationsForNodeAggregate( try { $rows = $this->dbal->fetchAllAssociative($ingoingHierarchyRelationsStatement, $parameters, $types); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, node aggregate id %s and dimension space points %s from database: %s', $contentStreamDbId->value, $nodeAggregateId->value, $dimensionSpacePointSet?->toJson() ?? '[any]', $e->getMessage()), 1716476743, $e); + throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, node aggregate id %s and dimension space points %s from database: %s', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $dimensionSpacePointSet?->toJson() ?? '[any]', $e->getMessage()), 1716476743, $e); } return array_map($this->mapRawDataToHierarchyRelation(...), $rows); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index 5b83602c81e..a68fed6f5e8 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -7,7 +7,7 @@ use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Query\QueryBuilder; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindRootNodeAggregatesFilter; @@ -48,10 +48,10 @@ public function buildBasicNodeAggregateQuery(): QueryBuilder ->from($this->tableNames->node(), 'n') ->innerJoin('n', $this->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->where('h.contentstreamdbid = :contentStreamDbId'); + ->where('h.contentstreamdbid = :contentStreamDbIds'); } - public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamDbId $contentStreamDbId): QueryBuilder + public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamDbIds $contentStreamDbIds): QueryBuilder { return $this->createQueryBuilder() ->select('cn.*, ch.contentstreamdbid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') @@ -60,21 +60,25 @@ public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregat ->innerJoin('ch', $this->tableNames->dimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') ->innerJoin('ch', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('pn.nodeaggregateid = :parentNodeAggregateId') - ->andWhere('ch.contentstreamdbid = :contentStreamDbId') + ->andWhere('ch.contentstreamdbid = :contentStreamDbIds') ->orderBy('ch.position') ->setParameters([ 'parentNodeAggregateId' => $parentNodeAggregateId->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER ]); } - public function buildFindRootNodeAggregatesQuery(ContentStreamDbId $contentStreamDbId, FindRootNodeAggregatesFilter $filter): QueryBuilder + public function buildFindRootNodeAggregatesQuery(ContentStreamDbIds $contentStreamDbIds, FindRootNodeAggregatesFilter $filter): QueryBuilder { $queryBuilder = $this->buildBasicNodeAggregateQuery() ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') ->setParameters([ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), 'rootEdgeParentAnchorId' => NodeRelationAnchorPoint::forRootEdge()->value, + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER ]); if ($filter->nodeTypeName !== null) { @@ -84,17 +88,17 @@ public function buildFindRootNodeAggregatesQuery(ContentStreamDbId $contentStrea return $queryBuilder; } - public function buildBasicNodeQuery(ContentStreamDbId $contentStreamDbId, DimensionSpacePoint $dimensionSpacePoint, string $nodeTableAlias = 'n', string $select = 'n.*, h.subtreetags'): QueryBuilder + public function buildBasicNodeQuery(ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint, string $nodeTableAlias = 'n', string $select = 'n.*, h.subtreetags'): QueryBuilder { return $this->createQueryBuilder() ->select($select) ->from($this->tableNames->node(), $nodeTableAlias) ->innerJoin($nodeTableAlias, $this->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') - ->where('h.contentstreamdbid = :contentStreamDbId')->setParameter('contentStreamDbId', $contentStreamDbId->value) + ->where('h.contentstreamdbid = :contentStreamDbIds')->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } - public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamDbId $contentStreamDbId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { return $this->createQueryBuilder() ->select('n.*, h.subtreetags') @@ -102,11 +106,11 @@ public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) - ->andWhere('h.contentstreamdbid = :contentStreamDbId')->setParameter('contentStreamDbId', $contentStreamDbId->value) + ->andWhere('h.contentstreamdbid = :contentStreamDbIds')->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } - public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ContentStreamDbId $contentStreamDbId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { return $this->createQueryBuilder() ->select('pn.*, ch.subtreetags') @@ -115,37 +119,37 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ->innerJoin('pn', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) - ->andWhere('ph.contentstreamdbid = :contentStreamDbId')->setParameter('contentStreamDbId', $contentStreamDbId->value) - ->andWhere('ch.contentstreamdbid = :contentStreamDbId') + ->andWhere('ph.contentstreamdbid = :contentStreamDbIds')->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) + ->andWhere('ch.contentstreamdbid = :contentStreamDbIds') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash'); } - public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamDbId $contentStreamDbId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { $sharedSubQuery = $this->createQueryBuilder() ->from($this->tableNames->hierarchyRelation(), 'sh') ->innerJoin('sh', $this->tableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') ->where('sn.nodeaggregateid = :siblingNodeAggregateId') - ->andWhere('sh.contentstreamdbid = :contentStreamDbId') + ->andWhere('sh.contentstreamdbid = :contentStreamDbIds') ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); $parentNodeAnchorSubQuery = (clone $sharedSubQuery)->select('sh.parentnodeanchor'); $siblingPositionSubQuery = (clone $sharedSubQuery)->select('sh.position'); - return $this->buildBasicNodeQuery($contentStreamDbId, $dimensionSpacePoint) + return $this->buildBasicNodeQuery($contentStreamDbIds, $dimensionSpacePoint) ->andWhere('h.parentnodeanchor = (' . $parentNodeAnchorSubQuery->getSQL() . ')') ->andWhere('n.nodeaggregateid != :siblingNodeAggregateId')->setParameter('siblingNodeAggregateId', $siblingNodeAggregateId->value) ->andWhere('h.position ' . ($preceding ? '<' : '>') . ' (' . $siblingPositionSubQuery->getSQL() . ')') ->orderBy('h.position', $preceding ? 'DESC' : 'ASC'); } - public function buildBasicNodesCteQuery(NodeAggregateId $entryNodeAggregateId, ContentStreamDbId $contentStreamDbId, DimensionSpacePoint $dimensionSpacePoint, string $cteName = 'ancestry', string $cteAlias = 'pn'): QueryBuilder + public function buildBasicNodesCteQuery(NodeAggregateId $entryNodeAggregateId, ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint, string $cteName = 'ancestry', string $cteAlias = 'pn'): QueryBuilder { return $this->createQueryBuilder() ->select('*') ->from($cteName, $cteAlias) - ->setParameter('contentStreamDbId', $contentStreamDbId->value) + ->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); } From 7189671acf5c76334919b1a87021f49f3a7b386a Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 12 Apr 2026 09:34:10 +0200 Subject: [PATCH 02/17] WIP: Copy hierarchy relations on write implemented for set properties and query hierarchy relation rows by taking multiple overlaying content stream db ids per content stream in account --- .../DoctrineDbalContentGraphProjection.php | 222 +++++++++++------- .../DoctrineDbalContentGraphSchemaBuilder.php | 10 +- .../Domain/Projection/ContentStreamDbId.php | 8 + .../Domain/Projection/ContentStreamDbIds.php | 12 +- .../Projection/Feature/ContentStream.php | 3 - .../Domain/Projection/Feature/NodeRemoval.php | 90 +++++-- .../Domain/Projection/HierarchyRelation.php | 2 +- .../src/Domain/Repository/ContentGraph.php | 12 +- .../Repository/ContentStreamDbIdFinder.php | 2 + .../Repository/ProjectionContentGraph.php | 20 +- .../src/HierarchyRelationQueryBuilder.php | 31 +++ .../src/NodeQueryBuilder.php | 40 ++-- 12 files changed, 288 insertions(+), 164 deletions(-) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 7155168cc41..0763736fe6f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -211,49 +211,31 @@ private function whenContentStreamWasClosed(ContentStreamWasClosed $event): void private function whenContentStreamWasCreated(ContentStreamWasCreated $event): void { $this->createContentStream($event->contentStreamId); + $this->dbal->insert($this->tableNames->contentStreamId(), [ + 'id' => $event->contentStreamId->value, + ]); } private function whenContentStreamWasForked(ContentStreamWasForked $event): void { $this->createContentStream($event->newContentStreamId, $event->sourceContentStreamId, $event->versionOfSourceContentStream); - $newContentStreamDbId = $this->contentStreamDbIdFinder->getContentStreamDbId($event->newContentStreamId); $sourceContentStreamDbId = $this->contentStreamDbIdFinder->getContentStreamDbId($event->sourceContentStreamId); - // - // 1) Copy HIERARCHY RELATIONS (this is the MAIN OPERATION here) - // - $insertRelationStatement = <<tableNames->hierarchyRelation()} ( - parentnodeanchor, - childnodeanchor, - position, - dimensionspacepointhash, - subtreetags, - contentstreamdbid - ) - SELECT - h.parentnodeanchor, - h.childnodeanchor, - h.position, - h.dimensionspacepointhash, - h.subtreetags, - :newContentStreamDbId AS contentstreamdbid - FROM - {$this->tableNames->hierarchyRelation()} h - WHERE h.contentstreamdbid = :sourceContentStreamDbId - SQL; - try { - $this->dbal->executeStatement($insertRelationStatement, [ - 'newContentStreamDbId' => $newContentStreamDbId->current()->value, - 'sourceContentStreamDbId' => $sourceContentStreamDbId->current()->value + $this->dbal->insert($this->tableNames->contentStreamId(), [ + 'id' => $event->sourceContentStreamId, + ]); + + foreach ($sourceContentStreamDbId->items as $sourceDbId) { + $this->dbal->insert($this->tableNames->contentStreamId(), [ + 'id' => $event->newContentStreamId, + 'dbId' => $sourceDbId->value ]); - } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to insert hierarchy relation: %s', $e->getMessage()), 1716489211, $e); } - // 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->dbal->insert($this->tableNames->contentStreamId(), [ + 'id' => $event->newContentStreamId, + ]); } private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): void @@ -261,46 +243,55 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo $contentStreamDbIds = $this->contentStreamDbIdFinder->getContentStreamDbId($event->contentStreamId); // Drop hierarchy relations - $deleteHierarchyRelationStatement = <<tableNames->hierarchyRelation()} WHERE contentstreamdbid IN (:contentStreamDbIds) - SQL; - try { - $this->dbal->executeStatement($deleteHierarchyRelationStatement, [ - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray() - ]); - } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to delete hierarchy relations: %s', $e->getMessage()), 1716489265, $e); - } + // TODO reimplement + // $deleteHierarchyRelationStatement = <<tableNames->hierarchyRelation()} WHERE contentstreamdbid IN (:contentStreamDbIds) + // SQL; + // try { + // $this->dbal->executeStatement($deleteHierarchyRelationStatement, [ + // 'contentStreamDbIds' => $contentStreamDbIds->toIntArray() + // ], [ + // 'contentStreamDbIds' => ArrayParameterType::INTEGER, + // ]); + // } catch (DBALException $e) { + // throw new \RuntimeException(sprintf('Failed to delete hierarchy relations: %s', $e->getMessage()), 1716489265, $e); + // } // Drop non-referenced nodes (which do not have a hierarchy relation anymore) - $deleteNodesStatement = <<tableNames->node()} - WHERE NOT EXISTS ( - SELECT 1 FROM {$this->tableNames->hierarchyRelation()} - WHERE {$this->tableNames->hierarchyRelation()}.childnodeanchor = {$this->tableNames->node()}.relationanchorpoint - ) - SQL; - try { - $this->dbal->executeStatement($deleteNodesStatement); - } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to delete non-referenced nodes: %s', $e->getMessage()), 1716489294, $e); - } + // TODO reimplement + // $deleteNodesStatement = <<tableNames->node()} + // WHERE NOT EXISTS ( + // SELECT 1 FROM {$this->tableNames->hierarchyRelation()} + // WHERE {$this->tableNames->hierarchyRelation()}.childnodeanchor = {$this->tableNames->node()}.relationanchorpoint + // ) + // SQL; + // try { + // $this->dbal->executeStatement($deleteNodesStatement); + // } catch (DBALException $e) { + // throw new \RuntimeException(sprintf('Failed to delete non-referenced nodes: %s', $e->getMessage()), 1716489294, $e); + // } // Drop non-referenced reference relations (i.e. because the referenced nodes are gone by now) - $deleteReferenceRelationsStatement = <<tableNames->referenceRelation()} - WHERE NOT EXISTS ( - SELECT 1 FROM {$this->tableNames->node()} - WHERE {$this->tableNames->node()}.relationanchorpoint = {$this->tableNames->referenceRelation()}.nodeanchorpoint - ) - SQL; - try { - $this->dbal->executeStatement($deleteReferenceRelationsStatement); - } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to delete non-referenced reference relations: %s', $e->getMessage()), 1716489328, $e); - } + // TODO reimplement + // $deleteReferenceRelationsStatement = <<tableNames->referenceRelation()} + // WHERE NOT EXISTS ( + // SELECT 1 FROM {$this->tableNames->node()} + // WHERE {$this->tableNames->node()}.relationanchorpoint = {$this->tableNames->referenceRelation()}.nodeanchorpoint + // ) + // SQL; + // try { + // $this->dbal->executeStatement($deleteReferenceRelationsStatement); + // } catch (DBALException $e) { + // throw new \RuntimeException(sprintf('Failed to delete non-referenced reference relations: %s', $e->getMessage()), 1716489328, $e); + // } $this->removeContentStream($event->contentStreamId); + + $this->dbal->delete($this->tableNames->contentStreamId(), [ + 'id' => $event->contentStreamId->value, + ]); } private function whenContentStreamWasReopened(ContentStreamWasReopened $event): void @@ -768,7 +759,7 @@ private function updateNodeRecordWithCopyOnWrite( callable $operations ): mixed { $contentStreamDbIds = $this->projectionContentGraph->getAllContentStreamDbIdsAnchorPointIsContainedIn($anchorPoint); - if (count($contentStreamDbIds) > 1) { + if (!$contentStreamDbIds->equals($contentStreamDbIdsWhereWriteOccurs->current())) { // Copy on Write needed! // Copy on Write is a purely "Content Stream" related concept; // thus we do not care about different DimensionSpacePoints here (but we copy all edges) @@ -782,32 +773,81 @@ private function updateNodeRecordWithCopyOnWrite( // 2) reconnect all edges belonging to this content stream to the new "copied node". // IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges. - $updateHierarchyRelationStatement = <<tableNames->hierarchyRelation()} h - SET - -- if our (copied) node is the child, we update h.childNodeAnchor - h.childnodeanchor = IF(h.childnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.childnodeanchor), - - -- if our (copied) node is the parent, we update h.parentNodeAnchor - h.parentnodeanchor = IF(h.parentnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.parentnodeanchor) - WHERE - :originalNodeAnchor IN (h.childnodeanchor, h.parentnodeanchor) - AND h.contentstreamdbid IN (:contentStreamDbIds) - SQL; - try { - $this->dbal->executeStatement($updateHierarchyRelationStatement, [ - 'newNodeAnchor' => $copiedNode->relationAnchorPoint->value, - 'originalNodeAnchor' => $anchorPoint->value, - 'contentStreamDbId' => $contentStreamDbIdsWhereWriteOccurs->value, - ]); - } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to update hierarchy relation: %s', $e->getMessage()), 1716486444, $e); + + if ($contentStreamDbIds->contain($contentStreamDbIdsWhereWriteOccurs->current())) { + $updateHierarchyRelationStatement = <<tableNames->hierarchyRelation()} h + SET + -- if our (copied) node is the child, we update h.childNodeAnchor + h.childnodeanchor = IF(h.childnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.childnodeanchor), + + -- if our (copied) node is the parent, we update h.parentNodeAnchor + h.parentnodeanchor = IF(h.parentnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.parentnodeanchor) + WHERE + :originalNodeAnchor IN (h.childnodeanchor, h.parentnodeanchor) + AND h.contentstreamdbid = :targetContentStreamDbId + SQL; + + try { + $this->dbal->executeStatement($updateHierarchyRelationStatement, [ + 'newNodeAnchor' => $copiedNode->relationAnchorPoint->value, + 'originalNodeAnchor' => $anchorPoint->value, + 'targetContentStreamDbId' => $contentStreamDbIdsWhereWriteOccurs->current()->value, + ]); + } catch (DBALException $e) { + throw new \RuntimeException(sprintf('Failed to update hierarchy relation: %s', $e->getMessage()), 1716486444, $e); + } + } else { + // todo is this correct + $copyHierarchyRelationStatement = <<tableNames->hierarchyRelation()} ( + id, + parentnodeanchor, + childnodeanchor, + position, + subtreetags, + dimensionspacepointhash, + contentstreamdbid + ) + SELECT + h.id, + -- if our (copied) node is the parent, we update h.parentNodeAnchor + IF(h.parentnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.parentnodeanchor) as parentnodeanchor, + -- if our (copied) node is the child, we update h.childNodeAnchor + IF(h.childnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.childnodeanchor) as childnodeanchor, + h.position, + h.subtreetags, + h.dimensionspacepointhash, + :targetContentStreamDbId as contentstreamdbid + FROM + {$this->tableNames->hierarchyRelation()} h + WHERE + :originalNodeAnchor IN (h.childnodeanchor, h.parentnodeanchor) + AND h.contentstreamdbid IN (:contentStreamDbIds) + SQL; + + try { + $this->dbal->executeStatement($copyHierarchyRelationStatement, [ + 'newNodeAnchor' => $copiedNode->relationAnchorPoint->value, + 'originalNodeAnchor' => $anchorPoint->value, + 'contentStreamDbIds' => $contentStreamDbIdsWhereWriteOccurs->toIntArray(), + 'targetContentStreamDbId' => $contentStreamDbIdsWhereWriteOccurs->current()->value, + ], [ + 'contentStreamDbIds' => ArrayParameterType::INTEGER, + ]); + } catch (DBALException $e) { + throw new \RuntimeException(sprintf('Failed to update hierarchy relation: %s', $e->getMessage()), 1716486444, $e); + } } + + + // reference relation rows need to be copied as well! - $this->copyReferenceRelations( - $anchorPoint, - $copiedNode->relationAnchorPoint - ); + // todo + // $this->copyReferenceRelations( + // $anchorPoint, + // $copiedNode->relationAnchorPoint + // ); return $result; } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 9f269183ae7..44294e1cb57 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -62,15 +62,19 @@ private function createNodeTable(AbstractPlatform $platform): Table private function createHierarchyRelationTable(AbstractPlatform $platform): Table { $table = self::createTable($this->tableNames->hierarchyRelation(), [ + (new Column('id', Type::getType(Types::INTEGER)))->setAutoincrement(true)->setNotnull(true), (new Column('position', self::type(Types::INTEGER)))->setNotnull(true), (new Column('contentstreamdbid', self::type(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForDimensionSpacePointHash('dimensionspacepointhash', $platform)->setNotnull(true), - DbalSchemaFactory::columnForNodeAnchorPoint('parentnodeanchor', $platform), - DbalSchemaFactory::columnForNodeAnchorPoint('childnodeanchor', $platform), + // todo nullable? + DbalSchemaFactory::columnForNodeAnchorPoint('parentnodeanchor', $platform)->setNotnull(false), + DbalSchemaFactory::columnForNodeAnchorPoint('childnodeanchor', $platform)->setNotnull(false), (new Column('subtreetags', self::type(Types::JSON))), ]); return $table + ->addIndex(['id']) + ->addUniqueIndex(['id', 'contentstreamdbid']) ->addIndex(['childnodeanchor']) ->addIndex(['contentstreamdbid']) ->addIndex(['parentnodeanchor']) @@ -141,7 +145,7 @@ private function createContentStreamIdTable(AbstractPlatform $platform): Table ]); return $contentStreamIdTable - ->addUniqueIndex(['dbId']) + ->addIndex(['dbId']) ->setPrimaryKey(['id', 'dbId']); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbId.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbId.php index ecce233283c..71fe5cca43f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbId.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbId.php @@ -12,6 +12,14 @@ private function __construct( public int $value ) { + if ($value < 0) { + throw new \InvalidArgumentException('A ContentStreamDbId cannot be negative, got %d', $value); + } + } + + public function equals(ContentStreamDbId $id): bool + { + return $this->value === $id->value; } public static function fromInt(int $value): self diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbIds.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbIds.php index 8970dd19c4c..89feb7b59f7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbIds.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbIds.php @@ -50,12 +50,22 @@ public function current(): ContentStreamDbId return $this->max; } + public function equals(ContentStreamDbId $id): bool + { + return count($this->items) === 1 && array_key_exists($id->value, $this->items); + } + + public function contain(ContentStreamDbId $id): bool + { + return array_key_exists($id->value, $this->items); + } + /** * @return list */ public function toIntArray(): array { - return array_map(fn (ContentStreamDbId $id) => $id->value, $this->items); + return array_keys($this->items); } public function toDebugString(): string diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php index e10b23e3b99..08e2e151824 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php @@ -24,9 +24,6 @@ private function createContentStream(ContentStreamId $contentStreamId, ?ContentS 'closed' => 0, 'hasChanges' => 0 ]); - $this->dbal->insert($this->tableNames->contentStreamId(), [ - 'id' => $contentStreamId->value, - ]); } private function closeContentStream(ContentStreamId $contentStreamId): void diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php index e248b9ce7f7..cf6ada80e76 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php @@ -28,45 +28,85 @@ private function removeNodeAggregate(ContentStreamDbIds $contentStreamDbIds, Nod ); foreach ($ingoingRelations as $ingoingRelation) { - $this->removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes($ingoingRelation); + $this->removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes($ingoingRelation, $contentStreamDbIds); } } private function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes( - HierarchyRelation $ingoingRelation + HierarchyRelation $ingoingRelation, + ContentStreamDbIds $contentStreamDbIds, ): void { - $ingoingRelation->removeFromDatabase($this->dbal, $this->tableNames); + // $ingoingRelation->removeFromDatabase($this->dbal, $this->tableNames); foreach ( $this->projectionContentGraph->findOutgoingHierarchyRelationsForNode( $ingoingRelation->childNodeAnchor, - $ingoingRelation->contentStreamDbId, + $contentStreamDbIds, new DimensionSpacePointSet([$ingoingRelation->dimensionSpacePoint]) ) as $outgoingRelation ) { - $this->removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes($outgoingRelation); + $this->removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes($outgoingRelation, $contentStreamDbIds); } - // remove node itself if it does not have any incoming hierarchy relations anymore - // also remove outbound reference relations - $deleteRelationsStatement = <<tableNames->node()} n - LEFT JOIN {$this->tableNames->referenceRelation()} r ON r.nodeanchorpoint = n.relationanchorpoint - LEFT JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint - WHERE - n.relationanchorpoint = :anchorPointForNode - -- the following line means "left join leads to NO MATCHING hierarchyrelation" - AND h.contentstreamdbid IS NULL - SQL; - try { - $this->dbal->executeStatement($deleteRelationsStatement, [ - 'anchorPointForNode' => $ingoingRelation->childNodeAnchor->value, - ]); - } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to remove relations from database: %s', $e->getMessage()), 1716473385, $e); + if ($contentStreamDbIds->current()->equals($ingoingRelation->contentStreamDbId)) { + $ingoingRelation->removeFromDatabase($this->dbal, $this->tableNames); + // remove node itself if it does not have any incoming hierarchy relations anymore + // also remove outbound reference relations + $deleteRelationsStatement = <<tableNames->node()} n + LEFT JOIN {$this->tableNames->referenceRelation()} r ON r.nodeanchorpoint = n.relationanchorpoint + LEFT JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint + WHERE + n.relationanchorpoint = :anchorPointForNode + -- the following line means "left join leads to NO MATCHING hierarchyrelation" + AND h.contentstreamdbid IS NULL + SQL; + try { + $this->dbal->executeStatement($deleteRelationsStatement, [ + 'anchorPointForNode' => $ingoingRelation->childNodeAnchor->value, + ]); + } catch (DBALException $e) { + throw new \RuntimeException(sprintf('Failed to remove relations from database: %s', $e->getMessage()), 1716473385, $e); + } + } else { + $copyRemovedHierarchyRelationStatement = <<tableNames->hierarchyRelation()} ( + id, + parentnodeanchor, + childnodeanchor, + position, + subtreetags, + dimensionspacepointhash, + contentstreamdbid + ) + SELECT + h.id, + NULL as parentnodeanchor, + NULL as childnodeanchor, + h.position, + h.subtreetags, + h.dimensionspacepointhash, + :targetContentStreamDbId as contentstreamdbid + FROM + {$this->tableNames->hierarchyRelation()} h + WHERE + parentnodeanchor = :parentnodeanchor + AND childnodeanchor = :childnodeanchor + AND contentstreamdbid = :contentstreamdbid + AND dimensionspacepointhash = :dimensionspacepointhash + SQL; + + try { + $this->dbal->executeStatement($copyRemovedHierarchyRelationStatement, [ + 'targetContentStreamDbId' => $contentStreamDbIds->current()->value, + ...$ingoingRelation->getDatabaseId(), + ]); + } catch (DBALException $e) { + throw new \RuntimeException(sprintf('Failed to copy hierarchy relation: %s', $e->getMessage()), 1775978611, $e); + } } } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php index ef288309b79..92c4e6b9cbf 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php @@ -130,7 +130,7 @@ public function assignNewPosition(int $position, Connection $databaseConnection, /** * @return array - */ // todo rename? because ambiguous: + */ // todo rename? because ambiguous: todo use actual db id now public function getDatabaseId(): array { return [ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 92e7fc940bd..367246fcf33 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -202,9 +202,8 @@ public function findParentNodeAggregates( NodeAggregateId $childNodeAggregateId ): NodeAggregates { $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') - ->andWhere('ch.contentstreamdbid = :contentStreamDbIds') ->andWhere('cn.nodeaggregateid = :nodeAggregateId') ->setParameters([ 'nodeAggregateId' => $childNodeAggregateId->value, @@ -260,20 +259,17 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr $subQueryBuilder = $this->createQueryBuilder() ->select('pn.nodeaggregateid') ->from($this->nodeQueryBuilder->tableNames->node(), 'pn') - ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :childOriginDimensionSpacePointHash'), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') - ->where('ch.contentstreamdbid = :contentStreamDbIds') - ->andWhere('ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash') - ->andWhere('cn.nodeaggregateid = :childNodeAggregateId') + ->where('cn.nodeaggregateid = :childNodeAggregateId') ->andWhere('cn.origindimensionspacepointhash = :childOriginDimensionSpacePointHash'); $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.contentstreamdbid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') - ->andWhere('h.contentstreamdbid = :contentStreamDbIds') ->setParameters([ 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray(), 'childNodeAggregateId' => $childNodeAggregateId->value, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php index 7ffe0b20050..3bc4ea9f091 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php @@ -46,6 +46,8 @@ public function getContentStreamDbId(ContentStreamId $contentStreamId): ContentS if ($contentStreamDbIds === null) { $this->fillRuntimeCacheFromDatabase(); $contentStreamDbIds = $this->getFromRuntimeCache($contentStreamId); + // todo reenable runtime cache + $this->contentStreamIdRuntimeCache = []; } if ($contentStreamDbIds === null) { diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index d781800a018..9120f8eb1b0 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -24,6 +24,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; +use Neos\ContentGraph\DoctrineDbalAdapter\HierarchyRelationQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; @@ -39,10 +40,13 @@ */ class ProjectionContentGraph { + private HierarchyRelationQueryBuilder $hierarchyRelationQueryBuilder; + public function __construct( private readonly Connection $dbal, private readonly ContentGraphTableNames $tableNames, ) { + $this->hierarchyRelationQueryBuilder = new HierarchyRelationQueryBuilder($this->tableNames); } /** @@ -132,11 +136,10 @@ public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStrea DISTINCT n.relationanchorpoint FROM {$this->tableNames->node()} n - INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN {$this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream()} as h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND n.origindimensionspacepointhash = :originDimensionSpacePointHash - AND h.contentstreamdbid IN (:contentStreamDbIds) SQL; try { $relationAnchorPoints = $this->dbal->fetchFirstColumn($relationAnchorPointsStatement, [ @@ -468,10 +471,9 @@ public function findOutgoingHierarchyRelationsForNode( SELECT h.* FROM - {$this->tableNames->hierarchyRelation()} h + {$this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream()} h WHERE h.parentnodeanchor = :parentAnchorPoint - AND h.contentstreamdbid IN (:contentStreamDbIds) SQL; $parameters = [ 'parentAnchorPoint' => $parentAnchorPoint->value, @@ -544,11 +546,10 @@ public function findIngoingHierarchyRelationsForNodeAggregate( SELECT h.* FROM - {$this->tableNames->hierarchyRelation()} h + {$this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream()} h INNER JOIN {$this->tableNames->node()} n ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamdbid IN (:contentStreamDbIds) SQL; $parameters = [ 'nodeAggregateId' => $nodeAggregateId->value, @@ -570,12 +571,9 @@ public function findIngoingHierarchyRelationsForNodeAggregate( return array_map($this->mapRawDataToHierarchyRelation(...), $rows); } - /** - * @return array - */ public function getAllContentStreamDbIdsAnchorPointIsContainedIn( NodeRelationAnchorPoint $nodeRelationAnchorPoint - ): array { + ): ContentStreamDbIds { $contentStreamDbIdsStatement = <<value, $e->getMessage()), 1716478504, $e); } - return array_map(ContentStreamDbId::fromInt(...), $contentStreamDbIds); + return ContentStreamDbIds::fromArray($contentStreamDbIds); } /** diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php new file mode 100644 index 00000000000..4704529421f --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php @@ -0,0 +1,31 @@ +tableNames->hierarchyRelation()} as h + INNER JOIN (SELECT id, MAX(contentstreamdbid) as contentstreamdbid + FROM {$this->tableNames->hierarchyRelation()} + WHERE (contentstreamdbid IN (:contentStreamDbIds)) + GROUP BY id + ) AS hIds ON h.contentstreamdbid = hIds.contentstreamdbid AND h.id = hIds.id + {$whereClause} + ) + SQL; + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index a68fed6f5e8..5fbbafbd02e 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -35,10 +35,13 @@ */ final readonly class NodeQueryBuilder { + public HierarchyRelationQueryBuilder $hierarchyRelationQueryBuilder; + public function __construct( private Connection $connection, public ContentGraphTableNames $tableNames ) { + $this->hierarchyRelationQueryBuilder = new HierarchyRelationQueryBuilder($this->tableNames); } public function buildBasicNodeAggregateQuery(): QueryBuilder @@ -46,9 +49,8 @@ public function buildBasicNodeAggregateQuery(): QueryBuilder return $this->createQueryBuilder() ->select('n.*, h.contentstreamdbid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'n') - ->innerJoin('n', $this->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->where('h.contentstreamdbid = :contentStreamDbIds'); + ->innerJoin('n', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash'); } public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamDbIds $contentStreamDbIds): QueryBuilder @@ -56,11 +58,10 @@ public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregat return $this->createQueryBuilder() ->select('cn.*, ch.contentstreamdbid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'pn') - ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->tableNames->dimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') ->innerJoin('ch', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('pn.nodeaggregateid = :parentNodeAggregateId') - ->andWhere('ch.contentstreamdbid = :contentStreamDbIds') ->orderBy('ch.position') ->setParameters([ 'parentNodeAggregateId' => $parentNodeAggregateId->value, @@ -93,9 +94,9 @@ public function buildBasicNodeQuery(ContentStreamDbIds $contentStreamDbIds, Dime return $this->createQueryBuilder() ->select($select) ->from($this->tableNames->node(), $nodeTableAlias) - ->innerJoin($nodeTableAlias, $this->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') - ->where('h.contentstreamdbid = :contentStreamDbIds')->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); + ->innerJoin($nodeTableAlias, $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') + ->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) + ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder @@ -103,11 +104,11 @@ public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId return $this->createQueryBuilder() ->select('n.*, h.subtreetags') ->from($this->tableNames->node(), 'pn') - ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) - ->andWhere('h.contentstreamdbid = :contentStreamDbIds')->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); + ->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) + ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder @@ -115,24 +116,21 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, return $this->createQueryBuilder() ->select('pn.*, ch.subtreetags') ->from($this->tableNames->node(), 'pn') - ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') + // todo we calculate hierarchy rows twice -> optimise + ->innerJoin('pn', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') - ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) - ->andWhere('ph.contentstreamdbid = :contentStreamDbIds')->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) - ->andWhere('ch.contentstreamdbid = :contentStreamDbIds') - ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) - ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash'); + ->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) + ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { $sharedSubQuery = $this->createQueryBuilder() - ->from($this->tableNames->hierarchyRelation(), 'sh') + ->from($this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'sh') ->innerJoin('sh', $this->tableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') - ->where('sn.nodeaggregateid = :siblingNodeAggregateId') - ->andWhere('sh.contentstreamdbid = :contentStreamDbIds') - ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); + ->where('sn.nodeaggregateid = :siblingNodeAggregateId'); $parentNodeAnchorSubQuery = (clone $sharedSubQuery)->select('sh.parentnodeanchor'); $siblingPositionSubQuery = (clone $sharedSubQuery)->select('sh.position'); From 2892839a97c63f05b24b05c4461e4f5829317e28 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 12 Apr 2026 10:29:28 +0200 Subject: [PATCH 03/17] WIP: Copy hierarchy relations on write (node variation) --- .../DoctrineDbalContentGraphProjection.php | 3 + .../Projection/Feature/NodeVariation.php | 210 ++++++++++++------ .../Domain/Projection/HierarchyRelation.php | 38 +++- .../Projection/HierarchyRelationDbId.php | 34 +++ .../Repository/ProjectionContentGraph.php | 2 + 5 files changed, 212 insertions(+), 75 deletions(-) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelationDbId.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 0763736fe6f..903f0e3b276 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -15,6 +15,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\SubtreeTagging; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\Workspace; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelationDbId; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentStreamDbIdFinder; @@ -992,6 +993,7 @@ private function connectHierarchy( $inheritedSubtreeTags = NodeTags::create(SubtreeTags::createEmpty(), $parentSubtreeTags->all()); $hierarchyRelation = new HierarchyRelation( + HierarchyRelationDbId::createAutoIncremented(), $parentNodeAnchorPoint, $childNodeAnchorPoint, $contentStreamDbIds->current(), @@ -1092,6 +1094,7 @@ private function copyHierarchyRelationToDimensionSpacePoint( $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamDbIds, $newParent, $dimensionSpacePoint); $inheritedSubtreeTags = NodeTags::create($sourceHierarchyRelation->subtreeTags->withoutInherited()->all(), $parentSubtreeTags->withoutInherited()->all()); $copy = new HierarchyRelation( + HierarchyRelationDbId::createAutoIncremented(), $newParent, $newChild, $contentStreamDbIds->current(), diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php index 51c3a9d269e..8b90a3f0464 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php @@ -4,8 +4,9 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelationDbId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSiblings; @@ -21,16 +22,16 @@ */ trait NodeVariation { - private function createNodeSpecializationVariant(ContentStreamDbId $contentStreamDbId, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $specializationOrigin, InterdimensionalSiblings $specializationSiblings, EventEnvelope $eventEnvelope): void + private function createNodeSpecializationVariant(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $specializationOrigin, InterdimensionalSiblings $specializationSiblings, EventEnvelope $eventEnvelope): void { // Do the actual specialization $sourceNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamDbIds, $nodeAggregateId, $sourceOrigin->toDimensionSpacePoint() ); if (is_null($sourceNode)) { - throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbId->value), 1716498651); + throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498651); } $specializedNode = $this->copyNodeToDimensionSpacePoint( @@ -42,59 +43,71 @@ private function createNodeSpecializationVariant(ContentStreamDbId $contentStrea $uncoveredDimensionSpacePoints = $specializationSiblings->toDimensionSpacePointSet()->points; foreach ( $this->projectionContentGraph->findIngoingHierarchyRelationsForNodeAggregate( - $contentStreamDbId, + $contentStreamDbIds, $sourceNode->nodeAggregateId, $specializationSiblings->toDimensionSpacePointSet() ) as $hierarchyRelation ) { - $hierarchyRelation->assignNewChildNode( - $specializedNode->relationAnchorPoint, - $this->dbal, - $this->tableNames - ); + if ($contentStreamDbIds->current()->equals($hierarchyRelation->contentStreamDbId)) { + $hierarchyRelation->assignNewChildNode( + $specializedNode->relationAnchorPoint, + $this->dbal, + $this->tableNames + ); + } else { + $copiedHierarchyRelation = $hierarchyRelation->with( + childNodeAnchor: $specializedNode->relationAnchorPoint, + contentStreamDbId: $contentStreamDbIds->current(), + ); + $copiedHierarchyRelation->addToDatabase( + $this->dbal, + $this->tableNames + ); + } unset($uncoveredDimensionSpacePoints[$hierarchyRelation->dimensionSpacePointHash]); } if (!empty($uncoveredDimensionSpacePoints)) { $sourceParent = $this->projectionContentGraph->findParentNode( - $contentStreamDbId, + $contentStreamDbIds, $nodeAggregateId, $sourceOrigin, ); if (is_null($sourceParent)) { - throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbId->value), 1716498695); + throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498695); } foreach ($uncoveredDimensionSpacePoints as $uncoveredDimensionSpacePoint) { $parentNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamDbIds, $sourceParent->nodeAggregateId, $uncoveredDimensionSpacePoint ); if (is_null($parentNode)) { - throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the target parent node "%s" is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbId->value, $sourceParent->nodeAggregateId->value), 1716498734); + throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the target parent node "%s" is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString(), $sourceParent->nodeAggregateId->value), 1716498734); } - $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamDbId, $parentNode->relationAnchorPoint, $uncoveredDimensionSpacePoint); + $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamDbIds, $parentNode->relationAnchorPoint, $uncoveredDimensionSpacePoint); $specializationSucceedingSiblingNodeAggregateId = $specializationSiblings ->getSucceedingSiblingIdForDimensionSpacePoint($uncoveredDimensionSpacePoint); $specializationSucceedingSiblingNode = $specializationSucceedingSiblingNodeAggregateId ? $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamDbIds, $specializationSucceedingSiblingNodeAggregateId, $uncoveredDimensionSpacePoint ) : null; $hierarchyRelation = new HierarchyRelation( + HierarchyRelationDbId::createAutoIncremented(), $parentNode->relationAnchorPoint, $specializedNode->relationAnchorPoint, - $contentStreamDbId, + $contentStreamDbIds->current(), $uncoveredDimensionSpacePoint, $uncoveredDimensionSpacePoint->hash, $this->projectionContentGraph->determineHierarchyRelationPosition( $parentNode->relationAnchorPoint, $specializedNode->relationAnchorPoint, $specializationSucceedingSiblingNode?->relationAnchorPoint, - $contentStreamDbId, + $contentStreamDbIds, $uncoveredDimensionSpacePoint ), NodeTags::create(SubtreeTags::createEmpty(), $parentSubtreeTags->all()), @@ -105,17 +118,28 @@ private function createNodeSpecializationVariant(ContentStreamDbId $contentStrea foreach ( $this->projectionContentGraph->findOutgoingHierarchyRelationsForNodeAggregate( - $contentStreamDbId, + $contentStreamDbIds, $sourceNode->nodeAggregateId, $specializationSiblings->toDimensionSpacePointSet() ) as $hierarchyRelation ) { - $hierarchyRelation->assignNewParentNode( - $specializedNode->relationAnchorPoint, - null, - $this->dbal, - $this->tableNames - ); + if ($contentStreamDbIds->current()->equals($hierarchyRelation->contentStreamDbId)) { + $hierarchyRelation->assignNewParentNode( + $specializedNode->relationAnchorPoint, + null, + $this->dbal, + $this->tableNames + ); + } else { + $copiedHierarchyRelation = $hierarchyRelation->with( + parentNodeAnchor: $specializedNode->relationAnchorPoint, + contentStreamDbId: $contentStreamDbIds->current(), + ); + $copiedHierarchyRelation->addToDatabase( + $this->dbal, + $this->tableNames + ); + } } // Copy Reference Edges @@ -125,24 +149,24 @@ private function createNodeSpecializationVariant(ContentStreamDbId $contentStrea ); } - public function createNodeGeneralizationVariant(ContentStreamDbId $contentStreamDbId, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $generalizationOrigin, InterdimensionalSiblings $variantSucceedingSiblings, EventEnvelope $eventEnvelope): void + public function createNodeGeneralizationVariant(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $generalizationOrigin, InterdimensionalSiblings $variantSucceedingSiblings, EventEnvelope $eventEnvelope): void { // do the generalization $sourceNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamDbIds, $nodeAggregateId, $sourceOrigin->toDimensionSpacePoint() ); if (is_null($sourceNode)) { - throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbId->value), 1716498802); + throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498802); } $sourceParentNode = $this->projectionContentGraph->findParentNode( - $contentStreamDbId, + $contentStreamDbIds, $nodeAggregateId, $sourceOrigin ); if (is_null($sourceParentNode)) { - throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbId->value), 1716498857); + throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498857); } $generalizedNode = $this->copyNodeToDimensionSpacePoint( $sourceNode, @@ -153,16 +177,27 @@ public function createNodeGeneralizationVariant(ContentStreamDbId $contentStream $unassignedIngoingDimensionSpacePoints = $variantSucceedingSiblings->toDimensionSpacePointSet(); foreach ( $this->projectionContentGraph->findIngoingHierarchyRelationsForNodeAggregate( - $contentStreamDbId, + $contentStreamDbIds, $nodeAggregateId, $variantSucceedingSiblings->toDimensionSpacePointSet() ) as $existingIngoingHierarchyRelation ) { - $existingIngoingHierarchyRelation->assignNewChildNode( - $generalizedNode->relationAnchorPoint, - $this->dbal, - $this->tableNames - ); + if ($contentStreamDbIds->current()->equals($existingIngoingHierarchyRelation->contentStreamDbId)) { + $existingIngoingHierarchyRelation->assignNewChildNode( + $generalizedNode->relationAnchorPoint, + $this->dbal, + $this->tableNames + ); + } else { + $copiedHierarchyRelation = $existingIngoingHierarchyRelation->with( + childNodeAnchor: $generalizedNode->relationAnchorPoint, + contentStreamDbId: $contentStreamDbIds->current(), + ); + $copiedHierarchyRelation->addToDatabase( + $this->dbal, + $this->tableNames + ); + } $unassignedIngoingDimensionSpacePoints = $unassignedIngoingDimensionSpacePoints->getDifference( new DimensionSpacePointSet([ $existingIngoingHierarchyRelation->dimensionSpacePoint @@ -172,34 +207,45 @@ public function createNodeGeneralizationVariant(ContentStreamDbId $contentStream foreach ( $this->projectionContentGraph->findOutgoingHierarchyRelationsForNodeAggregate( - $contentStreamDbId, + $contentStreamDbIds, $nodeAggregateId, $variantSucceedingSiblings->toDimensionSpacePointSet() ) as $existingOutgoingHierarchyRelation ) { - $existingOutgoingHierarchyRelation->assignNewParentNode( - $generalizedNode->relationAnchorPoint, - null, - $this->dbal, - $this->tableNames - ); + if ($contentStreamDbIds->current()->equals($existingOutgoingHierarchyRelation->contentStreamDbId)) { + $existingOutgoingHierarchyRelation->assignNewParentNode( + $generalizedNode->relationAnchorPoint, + null, + $this->dbal, + $this->tableNames + ); + } else { + $copiedHierarchyRelation = $existingOutgoingHierarchyRelation->with( + parentNodeAnchor: $generalizedNode->relationAnchorPoint, + contentStreamDbId: $contentStreamDbIds->current(), + ); + $copiedHierarchyRelation->addToDatabase( + $this->dbal, + $this->tableNames + ); + } } if (count($unassignedIngoingDimensionSpacePoints) > 0) { $ingoingSourceHierarchyRelation = $this->projectionContentGraph->findIngoingHierarchyRelationsForNode( $sourceNode->relationAnchorPoint, - $contentStreamDbId, + $contentStreamDbIds, new DimensionSpacePointSet([$sourceOrigin->toDimensionSpacePoint()]) )[$sourceOrigin->hash] ?? null; if (is_null($ingoingSourceHierarchyRelation)) { - throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the ingoing hierarchy relation is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbId->value), 1716498940); + throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the ingoing hierarchy relation is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498940); } // the null case is caught by the NodeAggregate or its command handler foreach ($unassignedIngoingDimensionSpacePoints as $unassignedDimensionSpacePoint) { // The parent node aggregate might be varied as well, // so we need to find a parent node for each covered dimension space point $generalizationParentNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamDbIds, $sourceParentNode->nodeAggregateId, $unassignedDimensionSpacePoint ); @@ -209,7 +255,7 @@ public function createNodeGeneralizationVariant(ContentStreamDbId $contentStream $nodeAggregateId->value, $sourceOrigin->toJson(), $unassignedDimensionSpacePoint->toJson(), - $contentStreamDbId->value, + $contentStreamDbIds->toDebugString(), $sourceParentNode->nodeAggregateId->value ), 1716498961); } @@ -218,7 +264,7 @@ public function createNodeGeneralizationVariant(ContentStreamDbId $contentStream ->getSucceedingSiblingIdForDimensionSpacePoint($unassignedDimensionSpacePoint); $generalizationSucceedingSiblingNode = $generalizationSucceedingSiblingNodeAggregateId ? $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamDbIds, $generalizationSucceedingSiblingNodeAggregateId, $unassignedDimensionSpacePoint ) @@ -226,7 +272,7 @@ public function createNodeGeneralizationVariant(ContentStreamDbId $contentStream $this->copyHierarchyRelationToDimensionSpacePoint( $ingoingSourceHierarchyRelation, - $contentStreamDbId, + $contentStreamDbIds, $unassignedDimensionSpacePoint, $generalizationParentNode->relationAnchorPoint, $generalizedNode->relationAnchorPoint, @@ -242,16 +288,16 @@ public function createNodeGeneralizationVariant(ContentStreamDbId $contentStream ); } - public function createNodePeerVariant(ContentStreamDbId $contentStreamDbId, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $peerOrigin, InterdimensionalSiblings $peerSucceedingSiblings, EventEnvelope $eventEnvelope): void + public function createNodePeerVariant(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $peerOrigin, InterdimensionalSiblings $peerSucceedingSiblings, EventEnvelope $eventEnvelope): void { // Do the peer variant creation itself $sourceNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamDbIds, $nodeAggregateId, $sourceOrigin->toDimensionSpacePoint() ); if (is_null($sourceNode)) { - throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbId->value), 1716498802); + throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498802); } $peerNode = $this->copyNodeToDimensionSpacePoint( $sourceNode, @@ -262,16 +308,27 @@ public function createNodePeerVariant(ContentStreamDbId $contentStreamDbId, Node $unassignedIngoingDimensionSpacePoints = $peerSucceedingSiblings->toDimensionSpacePointSet(); foreach ( $this->projectionContentGraph->findIngoingHierarchyRelationsForNodeAggregate( - $contentStreamDbId, + $contentStreamDbIds, $nodeAggregateId, $peerSucceedingSiblings->toDimensionSpacePointSet() ) as $existingIngoingHierarchyRelation ) { - $existingIngoingHierarchyRelation->assignNewChildNode( - $peerNode->relationAnchorPoint, - $this->dbal, - $this->tableNames - ); + if ($contentStreamDbIds->current()->equals($existingIngoingHierarchyRelation->contentStreamDbId)) { + $existingIngoingHierarchyRelation->assignNewChildNode( + $peerNode->relationAnchorPoint, + $this->dbal, + $this->tableNames + ); + } else { + $copiedHierarchyRelation = $existingIngoingHierarchyRelation->with( + childNodeAnchor: $peerNode->relationAnchorPoint, + contentStreamDbId: $contentStreamDbIds->current(), + ); + $copiedHierarchyRelation->addToDatabase( + $this->dbal, + $this->tableNames + ); + } $unassignedIngoingDimensionSpacePoints = $unassignedIngoingDimensionSpacePoints->getDifference( new DimensionSpacePointSet([ $existingIngoingHierarchyRelation->dimensionSpacePoint @@ -281,50 +338,61 @@ public function createNodePeerVariant(ContentStreamDbId $contentStreamDbId, Node foreach ( $this->projectionContentGraph->findOutgoingHierarchyRelationsForNodeAggregate( - $contentStreamDbId, + $contentStreamDbIds, $nodeAggregateId, $peerSucceedingSiblings->toDimensionSpacePointSet() ) as $existingOutgoingHierarchyRelation ) { - $existingOutgoingHierarchyRelation->assignNewParentNode( - $peerNode->relationAnchorPoint, - null, - $this->dbal, - $this->tableNames - ); + if ($contentStreamDbIds->current()->equals($existingOutgoingHierarchyRelation->contentStreamDbId)) { + $existingOutgoingHierarchyRelation->assignNewParentNode( + $peerNode->relationAnchorPoint, + null, + $this->dbal, + $this->tableNames + ); + } else { + $copiedHierarchyRelation = $existingOutgoingHierarchyRelation->with( + parentNodeAnchor: $peerNode->relationAnchorPoint, + contentStreamDbId: $contentStreamDbIds->current(), + ); + $copiedHierarchyRelation->addToDatabase( + $this->dbal, + $this->tableNames + ); + } } $sourceParentNode = $this->projectionContentGraph->findParentNode( - $contentStreamDbId, + $contentStreamDbIds, $nodeAggregateId, $sourceOrigin ); if (is_null($sourceParentNode)) { - throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbId->value), 1716498881); + throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498881); } foreach ($unassignedIngoingDimensionSpacePoints as $coveredDimensionSpacePoint) { // The parent node aggregate might be varied as well, // so we need to find a parent node for each covered dimension space point $peerParentNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamDbIds, $sourceParentNode->nodeAggregateId, $coveredDimensionSpacePoint ); if (is_null($peerParentNode)) { - throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the target parent node "%s" is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbId->value, $sourceParentNode->nodeAggregateId->value), 1716499016); + throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the target parent node "%s" is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString(), $sourceParentNode->nodeAggregateId->value), 1716499016); } $peerSucceedingSiblingNodeAggregateId = $peerSucceedingSiblings ->getSucceedingSiblingIdForDimensionSpacePoint($coveredDimensionSpacePoint); $peerSucceedingSiblingNode = $peerSucceedingSiblingNodeAggregateId ? $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamDbIds, $peerSucceedingSiblingNodeAggregateId, $coveredDimensionSpacePoint ) : null; $this->connectHierarchy( - $contentStreamDbId, + $contentStreamDbIds, $peerParentNode->relationAnchorPoint, $peerNode->relationAnchorPoint, new DimensionSpacePointSet([$coveredDimensionSpacePoint]), diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php index 92c4e6b9cbf..683f2535ddb 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php @@ -29,6 +29,7 @@ final readonly class HierarchyRelation { public function __construct( + public HierarchyRelationDbId $hierarchyRelationDbId, public NodeRelationAnchorPoint $parentNodeAnchor, public NodeRelationAnchorPoint $childNodeAnchor, public ContentStreamDbId $contentStreamDbId, @@ -39,6 +40,28 @@ public function __construct( ) { } + public function with( + ?HierarchyRelationDbId $hierarchyRelationDId = null, + ?NodeRelationAnchorPoint $parentNodeAnchor = null, + ?NodeRelationAnchorPoint $childNodeAnchor = null, + ?ContentStreamDbId $contentStreamDbId = null, + ?DimensionSpacePoint $dimensionSpacePoint = null, + ?string $dimensionSpacePointHash = null, + ?int $position = null, + ?NodeTags $subtreeTags = null, + ): self { + return new self( + hierarchyRelationDbId: $hierarchyRelationDId ?? $this->hierarchyRelationDbId, + parentNodeAnchor: $parentNodeAnchor ?? $this->parentNodeAnchor, + childNodeAnchor: $childNodeAnchor ?? $this->childNodeAnchor, + contentStreamDbId: $contentStreamDbId ?? $this->contentStreamDbId, + dimensionSpacePoint: $dimensionSpacePoint ?? $this->dimensionSpacePoint, + dimensionSpacePointHash: $dimensionSpacePointHash ?? $this->dimensionSpacePointHash, + position: $position ?? $this->position, + subtreeTags: $subtreeTags ?? $this->subtreeTags, + ); + } + public function addToDatabase(Connection $databaseConnection, ContentGraphTableNames $tableNames): void { $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, $tableNames); @@ -51,6 +74,7 @@ public function addToDatabase(Connection $databaseConnection, ContentGraphTableN try { $databaseConnection->insert($tableNames->hierarchyRelation(), [ + 'id' => $this->hierarchyRelationDbId->value, 'parentnodeanchor' => $this->parentNodeAnchor->value, 'childnodeanchor' => $this->childNodeAnchor->value, 'contentstreamdbid' => $this->contentStreamDbId->value, @@ -130,14 +154,20 @@ public function assignNewPosition(int $position, Connection $databaseConnection, /** * @return array - */ // todo rename? because ambiguous: todo use actual db id now + */ public function getDatabaseId(): array { + if (!$this->hierarchyRelationDbId->value) { + throw new \RuntimeException(sprintf('Hierarchy relation was not created in the database and does not have an id: %s', json_encode([ + 'parentnodeanchor' => $this->parentNodeAnchor->value, + 'childnodeanchor' => $this->childNodeAnchor->value, + 'contentstreamdbid' => $this->contentStreamDbId->value, + 'dimensionspacepointhash' => $this->dimensionSpacePointHash + ])), 1775979706); + } return [ - 'parentnodeanchor' => $this->parentNodeAnchor->value, - 'childnodeanchor' => $this->childNodeAnchor->value, + 'id' => $this->hierarchyRelationDbId->value, 'contentstreamdbid' => $this->contentStreamDbId->value, - 'dimensionspacepointhash' => $this->dimensionSpacePointHash ]; } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelationDbId.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelationDbId.php new file mode 100644 index 00000000000..7d6f349faeb --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelationDbId.php @@ -0,0 +1,34 @@ +value === $id->value; + } + + public static function fromInt(int $value): self + { + return new self($value); + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index 9120f8eb1b0..dfda39418f8 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -22,6 +22,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelationDbId; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentGraph\DoctrineDbalAdapter\HierarchyRelationQueryBuilder; @@ -614,6 +615,7 @@ private function mapRawDataToHierarchyRelation(array $rawData): HierarchyRelatio } return new HierarchyRelation( + HierarchyRelationDbId::fromInt((int)$rawData['id']), NodeRelationAnchorPoint::fromInteger((int)$rawData['parentnodeanchor']), NodeRelationAnchorPoint::fromInteger((int)$rawData['childnodeanchor']), ContentStreamDbId::fromInt((int)$rawData['contentstreamdbid']), From 8b51269e7decb19ba3ea46d7616d8ee272c7d2ed Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 12 Apr 2026 11:50:38 +0200 Subject: [PATCH 04/17] WIP: Node variation in `07-NodeRemoval/04-VariantRecreation.feature:48` --- .../src/Domain/Projection/Feature/NodeRemoval.php | 10 +++++----- .../Domain/Repository/ProjectionContentGraph.php | 13 ++++--------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php index cf6ada80e76..bf252e7386d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php @@ -86,6 +86,7 @@ private function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNode h.id, NULL as parentnodeanchor, NULL as childnodeanchor, + -- todo these fieds could be empty as well -- h.position, h.subtreetags, h.dimensionspacepointhash, @@ -93,16 +94,15 @@ private function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNode FROM {$this->tableNames->hierarchyRelation()} h WHERE - parentnodeanchor = :parentnodeanchor - AND childnodeanchor = :childnodeanchor - AND contentstreamdbid = :contentstreamdbid - AND dimensionspacepointhash = :dimensionspacepointhash + id = :id + AND contentstreamdbid = :contentStreamDbId SQL; try { $this->dbal->executeStatement($copyRemovedHierarchyRelationStatement, [ + 'id' => $ingoingRelation->hierarchyRelationDbId->value, + 'contentStreamDbId' => $ingoingRelation->contentStreamDbId->value, 'targetContentStreamDbId' => $contentStreamDbIds->current()->value, - ...$ingoingRelation->getDatabaseId(), ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to copy hierarchy relation: %s', $e->getMessage()), 1775978611, $e); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index dfda39418f8..cebc22c5559 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -513,12 +513,10 @@ public function findOutgoingHierarchyRelationsForNodeAggregate( SELECT h.* FROM - {$this->tableNames->hierarchyRelation()} h + {$this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash IN (:dimensionSpacePointHashes)')} h INNER JOIN {$this->tableNames->node()} n ON h.parentnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamdbid IN (:contentStreamDbIds) - AND h.dimensionspacepointhash IN (:dimensionSpacePointHashes) SQL; try { $rows = $this->dbal->fetchAllAssociative($outgoingHierarchyRelationsStatement, [ @@ -547,7 +545,7 @@ public function findIngoingHierarchyRelationsForNodeAggregate( SELECT h.* FROM - {$this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream()} h + {$this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream($dimensionSpacePointSet !== null ? 'WHERE h.dimensionspacepointhash IN (:dimensionSpacePointHashes)' : '')} h INNER JOIN {$this->tableNames->node()} n ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId @@ -555,15 +553,12 @@ public function findIngoingHierarchyRelationsForNodeAggregate( $parameters = [ 'nodeAggregateId' => $nodeAggregateId->value, 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + ...($dimensionSpacePointSet !== null ? ['dimensionSpacePointHashes' => $dimensionSpacePointSet->getPointHashes()] : []), ]; $types = [ 'contentStreamDbIds' => ArrayParameterType::INTEGER, + ...($dimensionSpacePointSet !== null ? ['dimensionSpacePointHashes' => ArrayParameterType::STRING] : []), ]; - if ($dimensionSpacePointSet !== null) { - $ingoingHierarchyRelationsStatement .= ' AND h.dimensionspacepointhash IN (:dimensionSpacePointHashes)'; - $parameters['dimensionSpacePointHashes'] = $dimensionSpacePointSet->getPointHashes(); - $types['dimensionSpacePointHashes'] = ArrayParameterType::STRING; - } try { $rows = $this->dbal->fetchAllAssociative($ingoingHierarchyRelationsStatement, $parameters, $types); } catch (DBALException $e) { From 7a151d783f31d05183b1c11a07b26678a889ecf2 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 14 Apr 2026 20:15:21 +0200 Subject: [PATCH 05/17] TASK: Rename temporary `ContentStreamDbId` to `ContentStreamLayer`(s) and tables --- .../src/ContentGraphReadModelAdapter.php | 10 +- .../src/ContentGraphTableNames.php | 4 +- .../DoctrineDbalContentGraphProjection.php | 172 +++++++++--------- ...trineDbalContentGraphProjectionFactory.php | 6 +- .../DoctrineDbalContentGraphSchemaBuilder.php | 28 +-- ...tStreamDbId.php => ContentStreamLayer.php} | 6 +- ...treamDbIds.php => ContentStreamLayers.php} | 16 +- .../Domain/Projection/Feature/NodeRemoval.php | 30 +-- .../Projection/Feature/NodeVariation.php | 104 +++++------ .../Domain/Projection/HierarchyRelation.php | 24 +-- ...lationDbId.php => HierarchyRelationId.php} | 6 +- .../src/Domain/Repository/ContentGraph.php | 60 +++--- .../Repository/ContentStreamDbIdFinder.php | 83 --------- .../Repository/ContentStreamLayerFinder.php | 47 +++++ .../src/Domain/Repository/ContentSubgraph.php | 64 +++---- .../Repository/ProjectionContentGraph.php | 154 ++++++++-------- .../src/HierarchyRelationQueryBuilder.php | 8 +- .../src/NodeQueryBuilder.php | 38 ++-- 18 files changed, 413 insertions(+), 447 deletions(-) rename Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/{ContentStreamDbId.php => ContentStreamLayer.php} (76%) rename Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/{ContentStreamDbIds.php => ContentStreamLayers.php} (75%) rename Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/{HierarchyRelationDbId.php => HierarchyRelationId.php} (81%) delete mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamLayerFinder.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php index 174964311e7..c50bc1b87b2 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php @@ -17,7 +17,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Query\QueryBuilder; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraph; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; @@ -51,10 +51,10 @@ public function getContentGraph(WorkspaceName $workspaceName): ContentGraph $currentContentStreamIdStatement = <<tableNames->workspace()} ws - JOIN {$this->tableNames->contentStreamId()} csIds ON csIds.id = ws.currentContentStreamId + JOIN {$this->tableNames->contentStreamLayer()} l ON l.contentStreamId = ws.currentContentStreamId WHERE ws.name = :workspaceName SQL; @@ -70,8 +70,8 @@ public function getContentGraph(WorkspaceName $workspaceName): ContentGraph } $firstRow = reset($rows); $currentContentStreamId = ContentStreamId::fromString($firstRow['currentContentStreamId']); - $contentStreamDbIds = ContentStreamDbIds::fromArray(array_column($rows, 'contentStreamDbId')); - return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $currentContentStreamId, $contentStreamDbIds); + $contentStreamLayers = ContentStreamLayers::fromArray(array_column($rows, 'contentStreamLayer')); + return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $currentContentStreamId, $contentStreamLayers); } public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php index 187ae7fe180..26644a082d0 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -52,8 +52,8 @@ public function contentStream(): string return $this->tableNamePrefix . '_contentstream'; } - public function contentStreamId(): string + public function contentStreamLayer(): string { - return $this->tableNamePrefix . '_contentstreamid'; + return $this->tableNamePrefix . '_contentstreamlayer'; } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 903f0e3b276..b3b5fbd5d05 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -7,7 +7,7 @@ use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception as DBALException; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\ContentStream; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeMove; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeRemoval; @@ -15,10 +15,10 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\SubtreeTagging; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\Workspace; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelationDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelationId; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentStreamDbIdFinder; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentStreamLayerFinder; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; @@ -95,7 +95,7 @@ public function __construct( private readonly ProjectionContentGraph $projectionContentGraph, private readonly ContentGraphTableNames $tableNames, private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository, - private readonly ContentStreamDbIdFinder $contentStreamDbIdFinder, + private readonly ContentStreamLayerFinder $contentStreamLayerFinder, private readonly ContentGraphReadModelInterface $contentGraphReadModel ) { } @@ -212,8 +212,8 @@ private function whenContentStreamWasClosed(ContentStreamWasClosed $event): void private function whenContentStreamWasCreated(ContentStreamWasCreated $event): void { $this->createContentStream($event->contentStreamId); - $this->dbal->insert($this->tableNames->contentStreamId(), [ - 'id' => $event->contentStreamId->value, + $this->dbal->insert($this->tableNames->contentStreamLayer(), [ + 'contentStreamId' => $event->contentStreamId->value, ]); } @@ -221,38 +221,38 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void { $this->createContentStream($event->newContentStreamId, $event->sourceContentStreamId, $event->versionOfSourceContentStream); - $sourceContentStreamDbId = $this->contentStreamDbIdFinder->getContentStreamDbId($event->sourceContentStreamId); + $sourceContentStreamLayer = $this->contentStreamLayerFinder->getContentStreamLayers($event->sourceContentStreamId); - $this->dbal->insert($this->tableNames->contentStreamId(), [ - 'id' => $event->sourceContentStreamId, + $this->dbal->insert($this->tableNames->contentStreamLayer(), [ + 'contentStreamId' => $event->sourceContentStreamId, ]); - foreach ($sourceContentStreamDbId->items as $sourceDbId) { - $this->dbal->insert($this->tableNames->contentStreamId(), [ - 'id' => $event->newContentStreamId, - 'dbId' => $sourceDbId->value + foreach ($sourceContentStreamLayer->items as $sourceDbId) { + $this->dbal->insert($this->tableNames->contentStreamLayer(), [ + 'contentStreamId' => $event->newContentStreamId, + 'contentStreamLayer' => $sourceDbId->value ]); } - $this->dbal->insert($this->tableNames->contentStreamId(), [ - 'id' => $event->newContentStreamId, + $this->dbal->insert($this->tableNames->contentStreamLayer(), [ + 'contentStreamId' => $event->newContentStreamId, ]); } private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): void { - $contentStreamDbIds = $this->contentStreamDbIdFinder->getContentStreamDbId($event->contentStreamId); + $contentStreamLayers = $this->contentStreamLayerFinder->getContentStreamLayers($event->contentStreamId); // Drop hierarchy relations // TODO reimplement // $deleteHierarchyRelationStatement = <<tableNames->hierarchyRelation()} WHERE contentstreamdbid IN (:contentStreamDbIds) + // DELETE FROM {$this->tableNames->hierarchyRelation()} WHERE contentstreamlayer IN (:contentStreamLayers) // SQL; // try { // $this->dbal->executeStatement($deleteHierarchyRelationStatement, [ - // 'contentStreamDbIds' => $contentStreamDbIds->toIntArray() + // 'contentStreamLayers' => $contentStreamLayers->toIntArray() // ], [ - // 'contentStreamDbIds' => ArrayParameterType::INTEGER, + // 'contentStreamLayers' => ArrayParameterType::INTEGER, // ]); // } catch (DBALException $e) { // throw new \RuntimeException(sprintf('Failed to delete hierarchy relations: %s', $e->getMessage()), 1716489265, $e); @@ -290,8 +290,8 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo $this->removeContentStream($event->contentStreamId); - $this->dbal->delete($this->tableNames->contentStreamId(), [ - 'id' => $event->contentStreamId->value, + $this->dbal->delete($this->tableNames->contentStreamLayer(), [ + 'contentStreamId' => $event->contentStreamId->value, ]); } @@ -312,7 +312,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded position, subtreetags, dimensionspacepointhash, - contentstreamdbid + contentstreamlayer ) SELECT h.parentnodeanchor, @@ -320,15 +320,15 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded h.position, h.subtreetags, :newDimensionSpacePointHash AS dimensionspacepointhash, - h.contentstreamdbid + h.contentstreamlayer FROM {$this->tableNames->hierarchyRelation()} h - WHERE h.contentstreamdbid IN (:contentStreamDbIds) + WHERE h.contentstreamlayer IN (:contentStreamLayers) AND h.dimensionspacepointhash = :sourceDimensionSpacePointHash SQL; try { $this->dbal->executeStatement($insertHierarchyRelationsStatement, [ - 'contentStreamDbId' => $this->getContentStreamDbId($event)->value, + 'contentStreamLayer' => $this->getContentStreamLayers($event)->value, 'sourceDimensionSpacePointHash' => $event->source->hash, 'newDimensionSpacePointHash' => $event->target->hash, ]); @@ -350,7 +350,7 @@ private function whenDimensionSpacePointWasMoved(DimensionSpacePointWasMoved $ev FROM {$this->tableNames->node()} n INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) AND h.dimensionspacepointhash = :dimensionSpacePointHash -- find only nodes which have their ORIGIN at the source DimensionSpacePoint, -- as we need to rewrite these origins (using copy on write) @@ -360,14 +360,14 @@ private function whenDimensionSpacePointWasMoved(DimensionSpacePointWasMoved $ev try { $relationAnchorPoints = $this->dbal->fetchFirstColumn($selectRelationsStatement, [ 'dimensionSpacePointHash' => $event->source->hash, - 'contentStreamDbId' => $this->getContentStreamDbId($event)->value + 'contentStreamLayer' => $this->getContentStreamLayers($event)->value ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load relation anchor points: %s', $e->getMessage()), 1716489628, $e); } foreach ($relationAnchorPoints as $relationAnchorPoint) { $this->updateNodeRecordWithCopyOnWrite( - $this->getContentStreamDbId($event), + $this->getContentStreamLayers($event), NodeRelationAnchorPoint::fromInteger($relationAnchorPoint), function (NodeRecord $nodeRecord) use ($event) { $nodeRecord->originDimensionSpacePoint = $event->target->coordinates; @@ -383,13 +383,13 @@ function (NodeRecord $nodeRecord) use ($event) { h.dimensionspacepointhash = :newDimensionSpacePointHash WHERE h.dimensionspacepointhash = :originalDimensionSpacePointHash - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) SQL; try { $this->dbal->executeStatement($updateHierarchyRelationsStatement, [ 'originalDimensionSpacePointHash' => $event->source->hash, 'newDimensionSpacePointHash' => $event->target->hash, - 'contentStreamDbId' => $this->getContentStreamDbId($event)->value, + 'contentStreamLayer' => $this->getContentStreamLayers($event)->value, ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to update hierarchy relations: %s', $e->getMessage()), 1716489951, $e); @@ -401,11 +401,11 @@ private function whenNodeAggregateNameWasChanged(NodeAggregateNameWasChanged $ev foreach ( $this->projectionContentGraph->getAnchorPointsForNodeAggregateInContentStream( $event->nodeAggregateId, - $this->getContentStreamDbId($event), + $this->getContentStreamLayers($event), ) as $anchorPoint ) { $this->updateNodeRecordWithCopyOnWrite( - $this->getContentStreamDbId($event), + $this->getContentStreamLayers($event), $anchorPoint, function (NodeRecord $node) use ($event, $eventEnvelope) { $node->nodeName = $event->newNodeName; @@ -420,10 +420,10 @@ function (NodeRecord $node) use ($event, $eventEnvelope) { private function whenNodeAggregateTypeWasChanged(NodeAggregateTypeWasChanged $event, EventEnvelope $eventEnvelope): void { - $anchorPoints = $this->projectionContentGraph->getAnchorPointsForNodeAggregateInContentStream($event->nodeAggregateId, $this->getContentStreamDbId($event)); + $anchorPoints = $this->projectionContentGraph->getAnchorPointsForNodeAggregateInContentStream($event->nodeAggregateId, $this->getContentStreamLayers($event)); foreach ($anchorPoints as $anchorPoint) { $this->updateNodeRecordWithCopyOnWrite( - $this->getContentStreamDbId($event), + $this->getContentStreamLayers($event), $anchorPoint, function (NodeRecord $node) use ($event, $eventEnvelope) { $node->nodeTypeName = $event->newNodeTypeName; @@ -438,18 +438,18 @@ function (NodeRecord $node) use ($event, $eventEnvelope) { private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event): void { - $this->moveNodeAggregate($this->getContentStreamDbId($event), $event->nodeAggregateId, $event->newParentNodeAggregateId, $event->succeedingSiblingsForCoverage); + $this->moveNodeAggregate($this->getContentStreamLayers($event), $event->nodeAggregateId, $event->newParentNodeAggregateId, $event->succeedingSiblingsForCoverage); } private function whenNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): void { - $this->removeNodeAggregate($this->getContentStreamDbId($event), $event->nodeAggregateId, $event->affectedCoveredDimensionSpacePoints); + $this->removeNodeAggregate($this->getContentStreamLayers($event), $event->nodeAggregateId, $event->affectedCoveredDimensionSpacePoints); } private function whenNodeAggregateWithNodeWasCreated(NodeAggregateWithNodeWasCreated $event, EventEnvelope $eventEnvelope): void { $this->createNodeWithHierarchy( - $this->getContentStreamDbId($event), + $this->getContentStreamLayers($event), $event->nodeAggregateId, $event->nodeTypeName, $event->parentNodeAggregateId, @@ -465,12 +465,12 @@ private function whenNodeAggregateWithNodeWasCreated(NodeAggregateWithNodeWasCre private function whenNodeGeneralizationVariantWasCreated(NodeGeneralizationVariantWasCreated $event, EventEnvelope $eventEnvelope): void { - $this->createNodeGeneralizationVariant($this->getContentStreamDbId($event), $event->nodeAggregateId, $event->sourceOrigin, $event->generalizationOrigin, $event->variantSucceedingSiblings, $eventEnvelope); + $this->createNodeGeneralizationVariant($this->getContentStreamLayers($event), $event->nodeAggregateId, $event->sourceOrigin, $event->generalizationOrigin, $event->variantSucceedingSiblings, $eventEnvelope); } private function whenNodePeerVariantWasCreated(NodePeerVariantWasCreated $event, EventEnvelope $eventEnvelope): void { - $this->createNodePeerVariant($this->getContentStreamDbId($event), $event->nodeAggregateId, $event->sourceOrigin, $event->peerOrigin, $event->peerSucceedingSiblings, $eventEnvelope); + $this->createNodePeerVariant($this->getContentStreamLayers($event), $event->nodeAggregateId, $event->sourceOrigin, $event->peerOrigin, $event->peerSucceedingSiblings, $eventEnvelope); } private function whenNodePropertiesWereSet(NodePropertiesWereSet $event, EventEnvelope $eventEnvelope): void @@ -479,7 +479,7 @@ private function whenNodePropertiesWereSet(NodePropertiesWereSet $event, EventEn ->getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStream( $event->getNodeAggregateId(), $event->getOriginDimensionSpacePoint(), - $this->getContentStreamDbId($event) + $this->getContentStreamLayers($event) ); if (is_null($anchorPoint)) { throw new \InvalidArgumentException( @@ -490,7 +490,7 @@ private function whenNodePropertiesWereSet(NodePropertiesWereSet $event, EventEn ); } $this->updateNodeRecordWithCopyOnWrite( - $this->getContentStreamDbId($event), + $this->getContentStreamLayers($event), $anchorPoint, function (NodeRecord $node) use ($event, $eventEnvelope) { $node->properties = $node->properties @@ -511,7 +511,7 @@ private function whenNodeReferencesWereSet(NodeReferencesWereSet $event, EventEn ->getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStream( $event->nodeAggregateId, $originDimensionSpacePoint, - $this->getContentStreamDbId($event) + $this->getContentStreamLayers($event) ); if (is_null($nodeAnchorPoint)) { @@ -525,7 +525,7 @@ private function whenNodeReferencesWereSet(NodeReferencesWereSet $event, EventEn } $this->updateNodeRecordWithCopyOnWrite( - $this->getContentStreamDbId($event), + $this->getContentStreamLayers($event), $nodeAnchorPoint, function (NodeRecord $node) use ($eventEnvelope) { $node->timestamps = $node->timestamps->with( @@ -539,7 +539,7 @@ function (NodeRecord $node) use ($eventEnvelope) { ->getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStream( $event->nodeAggregateId, $originDimensionSpacePoint, - $this->getContentStreamDbId($event) + $this->getContentStreamLayers($event) ); @@ -600,7 +600,7 @@ private function writeReferencesForTargetAnchorPoint(SerializedNodeReferences $n private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVariantWasCreated $event, EventEnvelope $eventEnvelope): void { - $this->createNodeSpecializationVariant($this->getContentStreamDbId($event), $event->nodeAggregateId, $event->sourceOrigin, $event->specializationOrigin, $event->specializationSiblings, $eventEnvelope); + $this->createNodeSpecializationVariant($this->getContentStreamLayers($event), $event->nodeAggregateId, $event->sourceOrigin, $event->specializationOrigin, $event->specializationSiblings, $eventEnvelope); } private function whenRootNodeAggregateDimensionsWereUpdated(RootNodeAggregateDimensionsWereUpdated $event): void @@ -610,7 +610,7 @@ private function whenRootNodeAggregateDimensionsWereUpdated(RootNodeAggregateDim $event->nodeAggregateId, /** the origin DSP of the root node is always the empty dimension ({@see whenRootNodeAggregateWithNodeWasCreated}) */ OriginDimensionSpacePoint::createWithoutDimensions(), - $this->getContentStreamDbId($event) + $this->getContentStreamLayers($event) ); if ($rootNodeAnchorPoint === null) { // should never happen. @@ -619,7 +619,7 @@ private function whenRootNodeAggregateDimensionsWereUpdated(RootNodeAggregateDim $ingoingRelations = $this->projectionContentGraph->findIngoingHierarchyRelationsForNode( $rootNodeAnchorPoint, - $this->getContentStreamDbId($event) + $this->getContentStreamLayers($event) ); $currentlyCoveredDimensionSpacePoints = []; @@ -631,7 +631,7 @@ private function whenRootNodeAggregateDimensionsWereUpdated(RootNodeAggregateDim // add hierarchy edges for newly added dimensions $this->connectHierarchy( - $this->getContentStreamDbId($event), + $this->getContentStreamLayers($event), NodeRelationAnchorPoint::forRootEdge(), $rootNodeAnchorPoint, $newlyCoveredDimensionSpacePoints, @@ -656,7 +656,7 @@ private function whenRootNodeAggregateWithNodeWasCreated(RootNodeAggregateWithNo ); $this->connectHierarchy( - $this->getContentStreamDbId($event), + $this->getContentStreamLayers($event), NodeRelationAnchorPoint::forRootEdge(), $node->relationAnchorPoint, $event->coveredDimensionSpacePoints, @@ -671,12 +671,12 @@ private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): vo private function whenSubtreeWasTagged(SubtreeWasTagged $event): void { - $this->addSubtreeTag($this->getContentStreamDbId($event), $event->nodeAggregateId, $event->affectedDimensionSpacePoints, $event->tag); + $this->addSubtreeTag($this->getContentStreamLayers($event), $event->nodeAggregateId, $event->affectedDimensionSpacePoints, $event->tag); } private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void { - $this->removeSubtreeTag($this->getContentStreamDbId($event), $event->nodeAggregateId, $event->affectedDimensionSpacePoints, $event->tag); + $this->removeSubtreeTag($this->getContentStreamLayers($event), $event->nodeAggregateId, $event->affectedDimensionSpacePoints, $event->tag); } private function whenWorkspaceBaseWorkspaceWasChanged(WorkspaceBaseWorkspaceWasChanged $event): void @@ -720,9 +720,9 @@ private function whenWorkspaceWasRemoved(WorkspaceWasRemoved $event): void /** --------------------------------- */ - public function getContentStreamDbId(EmbedsContentStreamId $event): ContentStreamDbIds + public function getContentStreamLayers(EmbedsContentStreamId $event): ContentStreamLayers { - return $this->contentStreamDbIdFinder->getContentStreamDbId($event->getContentStreamId()); + return $this->contentStreamLayerFinder->getContentStreamLayers($event->getContentStreamId()); } /** @@ -743,7 +743,7 @@ private function truncateDatabaseTables(): void $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->dimensionSpacePoints()); $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->workspace()); $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->contentStream()); - $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->contentStreamId()); + $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->contentStreamLayer()); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to truncate database tables for projection %s: %s', self::class, $e->getMessage()), 1716478318, $e); } @@ -755,12 +755,12 @@ private function truncateDatabaseTables(): void * @template T */ private function updateNodeRecordWithCopyOnWrite( - ContentStreamDbIds $contentStreamDbIdsWhereWriteOccurs, + ContentStreamLayers $contentStreamLayersWhereWriteOccurs, NodeRelationAnchorPoint $anchorPoint, callable $operations ): mixed { - $contentStreamDbIds = $this->projectionContentGraph->getAllContentStreamDbIdsAnchorPointIsContainedIn($anchorPoint); - if (!$contentStreamDbIds->equals($contentStreamDbIdsWhereWriteOccurs->current())) { + $contentStreamLayers = $this->projectionContentGraph->getAllContentStreamLayersAnchorPointIsContainedIn($anchorPoint); + if (!$contentStreamLayers->equals($contentStreamLayersWhereWriteOccurs->current())) { // Copy on Write needed! // Copy on Write is a purely "Content Stream" related concept; // thus we do not care about different DimensionSpacePoints here (but we copy all edges) @@ -775,7 +775,7 @@ private function updateNodeRecordWithCopyOnWrite( // 2) reconnect all edges belonging to this content stream to the new "copied node". // IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges. - if ($contentStreamDbIds->contain($contentStreamDbIdsWhereWriteOccurs->current())) { + if ($contentStreamLayers->contain($contentStreamLayersWhereWriteOccurs->current())) { $updateHierarchyRelationStatement = <<tableNames->hierarchyRelation()} h SET @@ -786,14 +786,14 @@ private function updateNodeRecordWithCopyOnWrite( h.parentnodeanchor = IF(h.parentnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.parentnodeanchor) WHERE :originalNodeAnchor IN (h.childnodeanchor, h.parentnodeanchor) - AND h.contentstreamdbid = :targetContentStreamDbId + AND h.contentstreamlayer = :targetContentStreamLayer SQL; try { $this->dbal->executeStatement($updateHierarchyRelationStatement, [ 'newNodeAnchor' => $copiedNode->relationAnchorPoint->value, 'originalNodeAnchor' => $anchorPoint->value, - 'targetContentStreamDbId' => $contentStreamDbIdsWhereWriteOccurs->current()->value, + 'targetContentStreamLayer' => $contentStreamLayersWhereWriteOccurs->current()->value, ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to update hierarchy relation: %s', $e->getMessage()), 1716486444, $e); @@ -808,7 +808,7 @@ private function updateNodeRecordWithCopyOnWrite( position, subtreetags, dimensionspacepointhash, - contentstreamdbid + contentstreamlayer ) SELECT h.id, @@ -819,22 +819,22 @@ private function updateNodeRecordWithCopyOnWrite( h.position, h.subtreetags, h.dimensionspacepointhash, - :targetContentStreamDbId as contentstreamdbid + :targetContentStreamLayer as contentstreamlayer FROM {$this->tableNames->hierarchyRelation()} h WHERE :originalNodeAnchor IN (h.childnodeanchor, h.parentnodeanchor) - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) SQL; try { $this->dbal->executeStatement($copyHierarchyRelationStatement, [ 'newNodeAnchor' => $copiedNode->relationAnchorPoint->value, 'originalNodeAnchor' => $anchorPoint->value, - 'contentStreamDbIds' => $contentStreamDbIdsWhereWriteOccurs->toIntArray(), - 'targetContentStreamDbId' => $contentStreamDbIdsWhereWriteOccurs->current()->value, + 'contentStreamLayers' => $contentStreamLayersWhereWriteOccurs->toIntArray(), + 'targetContentStreamLayer' => $contentStreamLayersWhereWriteOccurs->current()->value, ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to update hierarchy relation: %s', $e->getMessage()), 1716486444, $e); @@ -906,7 +906,7 @@ private static function initiatingDateTime(EventEnvelope $eventEnvelope): \DateT } private function createNodeWithHierarchy( - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, NodeTypeName $nodeTypeName, NodeAggregateId $parentNodeAggregateId, @@ -944,7 +944,7 @@ private function createNodeWithHierarchy( foreach ($missingParentRelations as $dimensionSpacePoint) { $parentNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbIds, + $contentStreamLayers, $parentNodeAggregateId, $dimensionSpacePoint ); @@ -952,7 +952,7 @@ private function createNodeWithHierarchy( $succeedingSiblingNodeAggregateId = $coverageSucceedingSiblings->getSucceedingSiblingIdForDimensionSpacePoint($dimensionSpacePoint); $succeedingSibling = $succeedingSiblingNodeAggregateId ? $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbIds, + $contentStreamLayers, $succeedingSiblingNodeAggregateId, $dimensionSpacePoint ) @@ -960,7 +960,7 @@ private function createNodeWithHierarchy( if ($parentNode) { $this->connectHierarchy( - $contentStreamDbIds, + $contentStreamLayers, $parentNode->relationAnchorPoint, $node->relationAnchorPoint, new DimensionSpacePointSet([$dimensionSpacePoint]), @@ -974,7 +974,7 @@ private function createNodeWithHierarchy( } private function connectHierarchy( - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, NodeRelationAnchorPoint $parentNodeAnchorPoint, NodeRelationAnchorPoint $childNodeAnchorPoint, DimensionSpacePointSet $dimensionSpacePointSet, @@ -985,18 +985,18 @@ private function connectHierarchy( $parentNodeAnchorPoint, null, $succeedingSiblingNodeAnchorPoint, - $contentStreamDbIds, + $contentStreamLayers, $dimensionSpacePoint ); - $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamDbIds, $parentNodeAnchorPoint, $dimensionSpacePoint); + $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamLayers, $parentNodeAnchorPoint, $dimensionSpacePoint); $inheritedSubtreeTags = NodeTags::create(SubtreeTags::createEmpty(), $parentSubtreeTags->all()); $hierarchyRelation = new HierarchyRelation( - HierarchyRelationDbId::createAutoIncremented(), + HierarchyRelationId::createAutoIncremented(), + $contentStreamLayers->current(), $parentNodeAnchorPoint, $childNodeAnchorPoint, - $contentStreamDbIds->current(), $dimensionSpacePoint, $dimensionSpacePoint->hash, $position, @@ -1011,14 +1011,14 @@ private function getRelationPosition( ?NodeRelationAnchorPoint $parentAnchorPoint, ?NodeRelationAnchorPoint $childAnchorPoint, ?NodeRelationAnchorPoint $succeedingSiblingAnchorPoint, - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint ): int { $position = $this->projectionContentGraph->determineHierarchyRelationPosition( $parentAnchorPoint, $childAnchorPoint, $succeedingSiblingAnchorPoint, - $contentStreamDbIds, + $contentStreamLayers, $dimensionSpacePoint ); @@ -1027,7 +1027,7 @@ private function getRelationPosition( $parentAnchorPoint, $childAnchorPoint, $succeedingSiblingAnchorPoint, - $contentStreamDbIds, + $contentStreamLayers, $dimensionSpacePoint ); } @@ -1039,7 +1039,7 @@ private function getRelationPositionAfterRecalculation( ?NodeRelationAnchorPoint $parentAnchorPoint, ?NodeRelationAnchorPoint $childAnchorPoint, ?NodeRelationAnchorPoint $succeedingSiblingAnchorPoint, - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint ): int { if (!$childAnchorPoint && !$parentAnchorPoint) { @@ -1054,12 +1054,12 @@ private function getRelationPositionAfterRecalculation( $hierarchyRelations = $parentAnchorPoint ? $this->projectionContentGraph->getOutgoingHierarchyRelationsForNodeAndSubgraph( $parentAnchorPoint, - $contentStreamDbIds, + $contentStreamLayers, $dimensionSpacePoint ) : $this->projectionContentGraph->getIngoingHierarchyRelationsForNodeAndSubgraph( $childAnchorPoint, - $contentStreamDbIds, + $contentStreamLayers, $dimensionSpacePoint ); @@ -1085,26 +1085,26 @@ private function getRelationPositionAfterRecalculation( private function copyHierarchyRelationToDimensionSpacePoint( HierarchyRelation $sourceHierarchyRelation, - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint, NodeRelationAnchorPoint $newParent, NodeRelationAnchorPoint $newChild, ?NodeRelationAnchorPoint $newSucceedingSibling = null, ): HierarchyRelation { - $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamDbIds, $newParent, $dimensionSpacePoint); + $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamLayers, $newParent, $dimensionSpacePoint); $inheritedSubtreeTags = NodeTags::create($sourceHierarchyRelation->subtreeTags->withoutInherited()->all(), $parentSubtreeTags->withoutInherited()->all()); $copy = new HierarchyRelation( - HierarchyRelationDbId::createAutoIncremented(), + HierarchyRelationId::createAutoIncremented(), + $contentStreamLayers->current(), $newParent, $newChild, - $contentStreamDbIds->current(), $dimensionSpacePoint, $dimensionSpacePoint->hash, $this->getRelationPosition( $newParent, $newChild, $newSucceedingSibling, - $contentStreamDbIds, + $contentStreamLayers, $dimensionSpacePoint ), $inheritedSubtreeTags, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php index a381d217a90..6d930aa4c02 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php @@ -5,7 +5,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter; use Doctrine\DBAL\Connection; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentStreamDbIdFinder; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentStreamLayerFinder; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; @@ -32,7 +32,7 @@ public function build( ); $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbal, $tableNames); - $contentStreamDbIdsRepository = new ContentStreamDbIdFinder($this->dbal, $tableNames); + $contentStreamLayerFinder = new ContentStreamLayerFinder($this->dbal, $tableNames); $nodeFactory = new NodeFactory( $projectionFactoryDependencies->contentRepositoryId, @@ -56,7 +56,7 @@ public function build( ), $tableNames, $dimensionSpacePointsRepository, - $contentStreamDbIdsRepository, + $contentStreamLayerFinder, $contentGraphReadModel ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 44294e1cb57..5aa5e799661 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -33,7 +33,7 @@ public function buildSchema(Connection $connection): Schema $this->createDimensionSpacePointsTable($connection->getDatabasePlatform()), $this->createWorkspaceTable($connection->getDatabasePlatform()), $this->createContentStreamTable($connection->getDatabasePlatform()), - $this->createContentStreamIdTable($connection->getDatabasePlatform()), + $this->createContentStreamLayerTable($connection->getDatabasePlatform()), ]); } @@ -63,8 +63,8 @@ private function createHierarchyRelationTable(AbstractPlatform $platform): Table { $table = self::createTable($this->tableNames->hierarchyRelation(), [ (new Column('id', Type::getType(Types::INTEGER)))->setAutoincrement(true)->setNotnull(true), + (new Column('contentstreamlayer', self::type(Types::INTEGER)))->setNotnull(true), (new Column('position', self::type(Types::INTEGER)))->setNotnull(true), - (new Column('contentstreamdbid', self::type(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForDimensionSpacePointHash('dimensionspacepointhash', $platform)->setNotnull(true), // todo nullable? DbalSchemaFactory::columnForNodeAnchorPoint('parentnodeanchor', $platform)->setNotnull(false), @@ -74,13 +74,13 @@ private function createHierarchyRelationTable(AbstractPlatform $platform): Table return $table ->addIndex(['id']) - ->addUniqueIndex(['id', 'contentstreamdbid']) + ->addUniqueIndex(['id', 'contentstreamlayer']) ->addIndex(['childnodeanchor']) - ->addIndex(['contentstreamdbid']) + ->addIndex(['contentstreamlayer']) ->addIndex(['parentnodeanchor']) - ->addIndex(['childnodeanchor', 'contentstreamdbid', 'dimensionspacepointhash', 'position']) - ->addIndex(['parentnodeanchor', 'contentstreamdbid', 'dimensionspacepointhash', 'position']) - ->addIndex(['contentstreamdbid', 'dimensionspacepointhash']); + ->addIndex(['childnodeanchor', 'contentstreamlayer', 'dimensionspacepointhash', 'position']) + ->addIndex(['parentnodeanchor', 'contentstreamlayer', 'dimensionspacepointhash', 'position']) + ->addIndex(['contentstreamlayer', 'dimensionspacepointhash']); } private function createDimensionSpacePointsTable(AbstractPlatform $platform): Table @@ -137,16 +137,16 @@ private function createContentStreamTable(AbstractPlatform $platform): Table ->setPrimaryKey(['id']); } - private function createContentStreamIdTable(AbstractPlatform $platform): Table + private function createContentStreamLayerTable(AbstractPlatform $platform): Table { - $contentStreamIdTable = self::createTable($this->tableNames->contentStreamId(), [ - DbalSchemaFactory::columnForContentStreamId('id', $platform)->setNotnull(true), - (new Column('dbId', Type::getType(Types::INTEGER)))->setAutoincrement(true)->setNotnull(true), + $contentStreamLayerTable = self::createTable($this->tableNames->contentStreamLayer(), [ + DbalSchemaFactory::columnForContentStreamId('contentStreamId', $platform)->setNotnull(true), + (new Column('contentStreamLayer', Type::getType(Types::INTEGER)))->setAutoincrement(true)->setNotnull(true), ]); - return $contentStreamIdTable - ->addIndex(['dbId']) - ->setPrimaryKey(['id', 'dbId']); + return $contentStreamLayerTable + ->addIndex(['contentStreamLayer']) + ->setPrimaryKey(['contentStreamId', 'contentStreamLayer']); } /** diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbId.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamLayer.php similarity index 76% rename from Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbId.php rename to Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamLayer.php index 71fe5cca43f..03515f44449 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbId.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamLayer.php @@ -7,17 +7,17 @@ /** * @internal */ -final readonly class ContentStreamDbId +final readonly class ContentStreamLayer { private function __construct( public int $value ) { if ($value < 0) { - throw new \InvalidArgumentException('A ContentStreamDbId cannot be negative, got %d', $value); + throw new \InvalidArgumentException('A ContentStreamLayer cannot be negative, got %d', $value); } } - public function equals(ContentStreamDbId $id): bool + public function equals(ContentStreamLayer $id): bool { return $this->value === $id->value; } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbIds.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamLayers.php similarity index 75% rename from Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbIds.php rename to Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamLayers.php index 89feb7b59f7..4a13a806a1f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamDbIds.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamLayers.php @@ -7,20 +7,20 @@ /** * @internal */ -final readonly class ContentStreamDbIds +final readonly class ContentStreamLayers { /** - * @param array $items + * @param array $items */ private function __construct( // todo remove all usages // public int $value, - private ContentStreamDbId $max, + private ContentStreamLayer $max, public array $items ) { } - public static function from(ContentStreamDbId ...$items): self + public static function from(ContentStreamLayer ...$items): self { if ($items === []) { throw new \InvalidArgumentException('Db ids must not be empty', 1775819046); @@ -41,21 +41,21 @@ public static function from(ContentStreamDbId ...$items): self public static function fromArray(array $array): self { return self::from( - ...array_map(ContentStreamDbId::fromInt(...), $array), + ...array_map(ContentStreamLayer::fromInt(...), $array), ); } - public function current(): ContentStreamDbId + public function current(): ContentStreamLayer { return $this->max; } - public function equals(ContentStreamDbId $id): bool + public function equals(ContentStreamLayer $id): bool { return count($this->items) === 1 && array_key_exists($id->value, $this->items); } - public function contain(ContentStreamDbId $id): bool + public function contain(ContentStreamLayer $id): bool { return array_key_exists($id->value, $this->items); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php index bf252e7386d..4a9f2e5a552 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php @@ -5,7 +5,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature; use Doctrine\DBAL\Exception as DBALException; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; @@ -17,38 +17,38 @@ */ trait NodeRemoval { - private function removeNodeAggregate(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedCoveredDimensionSpacePoints): void + private function removeNodeAggregate(ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedCoveredDimensionSpacePoints): void { // the focus here is to be correct; that's why the method is not overly performant (for now at least). We might // lateron find tricks to improve performance $ingoingRelations = $this->projectionContentGraph->findIngoingHierarchyRelationsForNodeAggregate( - $contentStreamDbIds, + $contentStreamLayers, $nodeAggregateId, $affectedCoveredDimensionSpacePoints ); foreach ($ingoingRelations as $ingoingRelation) { - $this->removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes($ingoingRelation, $contentStreamDbIds); + $this->removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes($ingoingRelation, $contentStreamLayers); } } private function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes( HierarchyRelation $ingoingRelation, - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, ): void { // $ingoingRelation->removeFromDatabase($this->dbal, $this->tableNames); foreach ( $this->projectionContentGraph->findOutgoingHierarchyRelationsForNode( $ingoingRelation->childNodeAnchor, - $contentStreamDbIds, + $contentStreamLayers, new DimensionSpacePointSet([$ingoingRelation->dimensionSpacePoint]) ) as $outgoingRelation ) { - $this->removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes($outgoingRelation, $contentStreamDbIds); + $this->removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes($outgoingRelation, $contentStreamLayers); } - if ($contentStreamDbIds->current()->equals($ingoingRelation->contentStreamDbId)) { + if ($contentStreamLayers->current()->equals($ingoingRelation->contentStreamLayer)) { $ingoingRelation->removeFromDatabase($this->dbal, $this->tableNames); // remove node itself if it does not have any incoming hierarchy relations anymore // also remove outbound reference relations @@ -62,7 +62,7 @@ private function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNode WHERE n.relationanchorpoint = :anchorPointForNode -- the following line means "left join leads to NO MATCHING hierarchyrelation" - AND h.contentstreamdbid IS NULL + AND h.contentstreamlayer IS NULL SQL; try { $this->dbal->executeStatement($deleteRelationsStatement, [ @@ -80,7 +80,7 @@ private function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNode position, subtreetags, dimensionspacepointhash, - contentstreamdbid + contentstreamlayer ) SELECT h.id, @@ -90,19 +90,19 @@ private function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNode h.position, h.subtreetags, h.dimensionspacepointhash, - :targetContentStreamDbId as contentstreamdbid + :targetContentStreamLayer as contentstreamlayer FROM {$this->tableNames->hierarchyRelation()} h WHERE id = :id - AND contentstreamdbid = :contentStreamDbId + AND contentstreamlayer = :contentStreamLayer SQL; try { $this->dbal->executeStatement($copyRemovedHierarchyRelationStatement, [ - 'id' => $ingoingRelation->hierarchyRelationDbId->value, - 'contentStreamDbId' => $ingoingRelation->contentStreamDbId->value, - 'targetContentStreamDbId' => $contentStreamDbIds->current()->value, + 'id' => $ingoingRelation->hierarchyRelationId->value, + 'contentStreamLayer' => $ingoingRelation->contentStreamLayer->value, + 'targetContentStreamLayer' => $contentStreamLayers->current()->value, ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to copy hierarchy relation: %s', $e->getMessage()), 1775978611, $e); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php index 8b90a3f0464..54ab6774d2d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php @@ -4,9 +4,9 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelationDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelationId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSiblings; @@ -22,16 +22,16 @@ */ trait NodeVariation { - private function createNodeSpecializationVariant(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $specializationOrigin, InterdimensionalSiblings $specializationSiblings, EventEnvelope $eventEnvelope): void + private function createNodeSpecializationVariant(ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $specializationOrigin, InterdimensionalSiblings $specializationSiblings, EventEnvelope $eventEnvelope): void { // Do the actual specialization $sourceNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbIds, + $contentStreamLayers, $nodeAggregateId, $sourceOrigin->toDimensionSpacePoint() ); if (is_null($sourceNode)) { - throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498651); + throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamLayers->toDebugString()), 1716498651); } $specializedNode = $this->copyNodeToDimensionSpacePoint( @@ -43,12 +43,12 @@ private function createNodeSpecializationVariant(ContentStreamDbIds $contentStre $uncoveredDimensionSpacePoints = $specializationSiblings->toDimensionSpacePointSet()->points; foreach ( $this->projectionContentGraph->findIngoingHierarchyRelationsForNodeAggregate( - $contentStreamDbIds, + $contentStreamLayers, $sourceNode->nodeAggregateId, $specializationSiblings->toDimensionSpacePointSet() ) as $hierarchyRelation ) { - if ($contentStreamDbIds->current()->equals($hierarchyRelation->contentStreamDbId)) { + if ($contentStreamLayers->current()->equals($hierarchyRelation->contentStreamLayer)) { $hierarchyRelation->assignNewChildNode( $specializedNode->relationAnchorPoint, $this->dbal, @@ -57,7 +57,7 @@ private function createNodeSpecializationVariant(ContentStreamDbIds $contentStre } else { $copiedHierarchyRelation = $hierarchyRelation->with( childNodeAnchor: $specializedNode->relationAnchorPoint, - contentStreamDbId: $contentStreamDbIds->current(), + contentStreamLayer: $contentStreamLayers->current(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, @@ -68,46 +68,46 @@ private function createNodeSpecializationVariant(ContentStreamDbIds $contentStre } if (!empty($uncoveredDimensionSpacePoints)) { $sourceParent = $this->projectionContentGraph->findParentNode( - $contentStreamDbIds, + $contentStreamLayers, $nodeAggregateId, $sourceOrigin, ); if (is_null($sourceParent)) { - throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498695); + throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamLayers->toDebugString()), 1716498695); } foreach ($uncoveredDimensionSpacePoints as $uncoveredDimensionSpacePoint) { $parentNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbIds, + $contentStreamLayers, $sourceParent->nodeAggregateId, $uncoveredDimensionSpacePoint ); if (is_null($parentNode)) { - throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the target parent node "%s" is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString(), $sourceParent->nodeAggregateId->value), 1716498734); + throw new \RuntimeException(sprintf('Failed to create node specialization variant for node "%s" in sub graph %s@%s because the target parent node "%s" is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamLayers->toDebugString(), $sourceParent->nodeAggregateId->value), 1716498734); } - $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamDbIds, $parentNode->relationAnchorPoint, $uncoveredDimensionSpacePoint); + $parentSubtreeTags = $this->subtreeTagsForHierarchyRelation($contentStreamLayers, $parentNode->relationAnchorPoint, $uncoveredDimensionSpacePoint); $specializationSucceedingSiblingNodeAggregateId = $specializationSiblings ->getSucceedingSiblingIdForDimensionSpacePoint($uncoveredDimensionSpacePoint); $specializationSucceedingSiblingNode = $specializationSucceedingSiblingNodeAggregateId ? $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbIds, + $contentStreamLayers, $specializationSucceedingSiblingNodeAggregateId, $uncoveredDimensionSpacePoint ) : null; $hierarchyRelation = new HierarchyRelation( - HierarchyRelationDbId::createAutoIncremented(), + HierarchyRelationId::createAutoIncremented(), + $contentStreamLayers->current(), $parentNode->relationAnchorPoint, $specializedNode->relationAnchorPoint, - $contentStreamDbIds->current(), $uncoveredDimensionSpacePoint, $uncoveredDimensionSpacePoint->hash, $this->projectionContentGraph->determineHierarchyRelationPosition( $parentNode->relationAnchorPoint, $specializedNode->relationAnchorPoint, $specializationSucceedingSiblingNode?->relationAnchorPoint, - $contentStreamDbIds, + $contentStreamLayers, $uncoveredDimensionSpacePoint ), NodeTags::create(SubtreeTags::createEmpty(), $parentSubtreeTags->all()), @@ -118,12 +118,12 @@ private function createNodeSpecializationVariant(ContentStreamDbIds $contentStre foreach ( $this->projectionContentGraph->findOutgoingHierarchyRelationsForNodeAggregate( - $contentStreamDbIds, + $contentStreamLayers, $sourceNode->nodeAggregateId, $specializationSiblings->toDimensionSpacePointSet() ) as $hierarchyRelation ) { - if ($contentStreamDbIds->current()->equals($hierarchyRelation->contentStreamDbId)) { + if ($contentStreamLayers->current()->equals($hierarchyRelation->contentStreamLayer)) { $hierarchyRelation->assignNewParentNode( $specializedNode->relationAnchorPoint, null, @@ -133,7 +133,7 @@ private function createNodeSpecializationVariant(ContentStreamDbIds $contentStre } else { $copiedHierarchyRelation = $hierarchyRelation->with( parentNodeAnchor: $specializedNode->relationAnchorPoint, - contentStreamDbId: $contentStreamDbIds->current(), + contentStreamLayer: $contentStreamLayers->current(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, @@ -149,24 +149,24 @@ private function createNodeSpecializationVariant(ContentStreamDbIds $contentStre ); } - public function createNodeGeneralizationVariant(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $generalizationOrigin, InterdimensionalSiblings $variantSucceedingSiblings, EventEnvelope $eventEnvelope): void + public function createNodeGeneralizationVariant(ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $generalizationOrigin, InterdimensionalSiblings $variantSucceedingSiblings, EventEnvelope $eventEnvelope): void { // do the generalization $sourceNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbIds, + $contentStreamLayers, $nodeAggregateId, $sourceOrigin->toDimensionSpacePoint() ); if (is_null($sourceNode)) { - throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498802); + throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamLayers->toDebugString()), 1716498802); } $sourceParentNode = $this->projectionContentGraph->findParentNode( - $contentStreamDbIds, + $contentStreamLayers, $nodeAggregateId, $sourceOrigin ); if (is_null($sourceParentNode)) { - throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498857); + throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamLayers->toDebugString()), 1716498857); } $generalizedNode = $this->copyNodeToDimensionSpacePoint( $sourceNode, @@ -177,12 +177,12 @@ public function createNodeGeneralizationVariant(ContentStreamDbIds $contentStrea $unassignedIngoingDimensionSpacePoints = $variantSucceedingSiblings->toDimensionSpacePointSet(); foreach ( $this->projectionContentGraph->findIngoingHierarchyRelationsForNodeAggregate( - $contentStreamDbIds, + $contentStreamLayers, $nodeAggregateId, $variantSucceedingSiblings->toDimensionSpacePointSet() ) as $existingIngoingHierarchyRelation ) { - if ($contentStreamDbIds->current()->equals($existingIngoingHierarchyRelation->contentStreamDbId)) { + if ($contentStreamLayers->current()->equals($existingIngoingHierarchyRelation->contentStreamLayer)) { $existingIngoingHierarchyRelation->assignNewChildNode( $generalizedNode->relationAnchorPoint, $this->dbal, @@ -191,7 +191,7 @@ public function createNodeGeneralizationVariant(ContentStreamDbIds $contentStrea } else { $copiedHierarchyRelation = $existingIngoingHierarchyRelation->with( childNodeAnchor: $generalizedNode->relationAnchorPoint, - contentStreamDbId: $contentStreamDbIds->current(), + contentStreamLayer: $contentStreamLayers->current(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, @@ -207,12 +207,12 @@ public function createNodeGeneralizationVariant(ContentStreamDbIds $contentStrea foreach ( $this->projectionContentGraph->findOutgoingHierarchyRelationsForNodeAggregate( - $contentStreamDbIds, + $contentStreamLayers, $nodeAggregateId, $variantSucceedingSiblings->toDimensionSpacePointSet() ) as $existingOutgoingHierarchyRelation ) { - if ($contentStreamDbIds->current()->equals($existingOutgoingHierarchyRelation->contentStreamDbId)) { + if ($contentStreamLayers->current()->equals($existingOutgoingHierarchyRelation->contentStreamLayer)) { $existingOutgoingHierarchyRelation->assignNewParentNode( $generalizedNode->relationAnchorPoint, null, @@ -222,7 +222,7 @@ public function createNodeGeneralizationVariant(ContentStreamDbIds $contentStrea } else { $copiedHierarchyRelation = $existingOutgoingHierarchyRelation->with( parentNodeAnchor: $generalizedNode->relationAnchorPoint, - contentStreamDbId: $contentStreamDbIds->current(), + contentStreamLayer: $contentStreamLayers->current(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, @@ -234,18 +234,18 @@ public function createNodeGeneralizationVariant(ContentStreamDbIds $contentStrea if (count($unassignedIngoingDimensionSpacePoints) > 0) { $ingoingSourceHierarchyRelation = $this->projectionContentGraph->findIngoingHierarchyRelationsForNode( $sourceNode->relationAnchorPoint, - $contentStreamDbIds, + $contentStreamLayers, new DimensionSpacePointSet([$sourceOrigin->toDimensionSpacePoint()]) )[$sourceOrigin->hash] ?? null; if (is_null($ingoingSourceHierarchyRelation)) { - throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the ingoing hierarchy relation is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498940); + throw new \RuntimeException(sprintf('Failed to create node generalization variant for node "%s" in sub graph %s@%s because the ingoing hierarchy relation is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamLayers->toDebugString()), 1716498940); } // the null case is caught by the NodeAggregate or its command handler foreach ($unassignedIngoingDimensionSpacePoints as $unassignedDimensionSpacePoint) { // The parent node aggregate might be varied as well, // so we need to find a parent node for each covered dimension space point $generalizationParentNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbIds, + $contentStreamLayers, $sourceParentNode->nodeAggregateId, $unassignedDimensionSpacePoint ); @@ -255,7 +255,7 @@ public function createNodeGeneralizationVariant(ContentStreamDbIds $contentStrea $nodeAggregateId->value, $sourceOrigin->toJson(), $unassignedDimensionSpacePoint->toJson(), - $contentStreamDbIds->toDebugString(), + $contentStreamLayers->toDebugString(), $sourceParentNode->nodeAggregateId->value ), 1716498961); } @@ -264,7 +264,7 @@ public function createNodeGeneralizationVariant(ContentStreamDbIds $contentStrea ->getSucceedingSiblingIdForDimensionSpacePoint($unassignedDimensionSpacePoint); $generalizationSucceedingSiblingNode = $generalizationSucceedingSiblingNodeAggregateId ? $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbIds, + $contentStreamLayers, $generalizationSucceedingSiblingNodeAggregateId, $unassignedDimensionSpacePoint ) @@ -272,7 +272,7 @@ public function createNodeGeneralizationVariant(ContentStreamDbIds $contentStrea $this->copyHierarchyRelationToDimensionSpacePoint( $ingoingSourceHierarchyRelation, - $contentStreamDbIds, + $contentStreamLayers, $unassignedDimensionSpacePoint, $generalizationParentNode->relationAnchorPoint, $generalizedNode->relationAnchorPoint, @@ -288,16 +288,16 @@ public function createNodeGeneralizationVariant(ContentStreamDbIds $contentStrea ); } - public function createNodePeerVariant(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $peerOrigin, InterdimensionalSiblings $peerSucceedingSiblings, EventEnvelope $eventEnvelope): void + public function createNodePeerVariant(ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $peerOrigin, InterdimensionalSiblings $peerSucceedingSiblings, EventEnvelope $eventEnvelope): void { // Do the peer variant creation itself $sourceNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbIds, + $contentStreamLayers, $nodeAggregateId, $sourceOrigin->toDimensionSpacePoint() ); if (is_null($sourceNode)) { - throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498802); + throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the source node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamLayers->toDebugString()), 1716498802); } $peerNode = $this->copyNodeToDimensionSpacePoint( $sourceNode, @@ -308,12 +308,12 @@ public function createNodePeerVariant(ContentStreamDbIds $contentStreamDbIds, No $unassignedIngoingDimensionSpacePoints = $peerSucceedingSiblings->toDimensionSpacePointSet(); foreach ( $this->projectionContentGraph->findIngoingHierarchyRelationsForNodeAggregate( - $contentStreamDbIds, + $contentStreamLayers, $nodeAggregateId, $peerSucceedingSiblings->toDimensionSpacePointSet() ) as $existingIngoingHierarchyRelation ) { - if ($contentStreamDbIds->current()->equals($existingIngoingHierarchyRelation->contentStreamDbId)) { + if ($contentStreamLayers->current()->equals($existingIngoingHierarchyRelation->contentStreamLayer)) { $existingIngoingHierarchyRelation->assignNewChildNode( $peerNode->relationAnchorPoint, $this->dbal, @@ -322,7 +322,7 @@ public function createNodePeerVariant(ContentStreamDbIds $contentStreamDbIds, No } else { $copiedHierarchyRelation = $existingIngoingHierarchyRelation->with( childNodeAnchor: $peerNode->relationAnchorPoint, - contentStreamDbId: $contentStreamDbIds->current(), + contentStreamLayer: $contentStreamLayers->current(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, @@ -338,12 +338,12 @@ public function createNodePeerVariant(ContentStreamDbIds $contentStreamDbIds, No foreach ( $this->projectionContentGraph->findOutgoingHierarchyRelationsForNodeAggregate( - $contentStreamDbIds, + $contentStreamLayers, $nodeAggregateId, $peerSucceedingSiblings->toDimensionSpacePointSet() ) as $existingOutgoingHierarchyRelation ) { - if ($contentStreamDbIds->current()->equals($existingOutgoingHierarchyRelation->contentStreamDbId)) { + if ($contentStreamLayers->current()->equals($existingOutgoingHierarchyRelation->contentStreamLayer)) { $existingOutgoingHierarchyRelation->assignNewParentNode( $peerNode->relationAnchorPoint, null, @@ -353,7 +353,7 @@ public function createNodePeerVariant(ContentStreamDbIds $contentStreamDbIds, No } else { $copiedHierarchyRelation = $existingOutgoingHierarchyRelation->with( parentNodeAnchor: $peerNode->relationAnchorPoint, - contentStreamDbId: $contentStreamDbIds->current(), + contentStreamLayer: $contentStreamLayers->current(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, @@ -363,36 +363,36 @@ public function createNodePeerVariant(ContentStreamDbIds $contentStreamDbIds, No } $sourceParentNode = $this->projectionContentGraph->findParentNode( - $contentStreamDbIds, + $contentStreamLayers, $nodeAggregateId, $sourceOrigin ); if (is_null($sourceParentNode)) { - throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString()), 1716498881); + throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the source parent node is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamLayers->toDebugString()), 1716498881); } foreach ($unassignedIngoingDimensionSpacePoints as $coveredDimensionSpacePoint) { // The parent node aggregate might be varied as well, // so we need to find a parent node for each covered dimension space point $peerParentNode = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbIds, + $contentStreamLayers, $sourceParentNode->nodeAggregateId, $coveredDimensionSpacePoint ); if (is_null($peerParentNode)) { - throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the target parent node "%s" is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamDbIds->toDebugString(), $sourceParentNode->nodeAggregateId->value), 1716499016); + throw new \RuntimeException(sprintf('Failed to create node peer variant for node "%s" in sub graph %s@%s because the target parent node "%s" is missing', $nodeAggregateId->value, $sourceOrigin->toJson(), $contentStreamLayers->toDebugString(), $sourceParentNode->nodeAggregateId->value), 1716499016); } $peerSucceedingSiblingNodeAggregateId = $peerSucceedingSiblings ->getSucceedingSiblingIdForDimensionSpacePoint($coveredDimensionSpacePoint); $peerSucceedingSiblingNode = $peerSucceedingSiblingNodeAggregateId ? $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbIds, + $contentStreamLayers, $peerSucceedingSiblingNodeAggregateId, $coveredDimensionSpacePoint ) : null; $this->connectHierarchy( - $contentStreamDbIds, + $contentStreamLayers, $peerParentNode->relationAnchorPoint, $peerNode->relationAnchorPoint, new DimensionSpacePointSet([$coveredDimensionSpacePoint]), diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php index 683f2535ddb..be88cd3cc84 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php @@ -29,10 +29,10 @@ final readonly class HierarchyRelation { public function __construct( - public HierarchyRelationDbId $hierarchyRelationDbId, + public HierarchyRelationId $hierarchyRelationId, + public ContentStreamLayer $contentStreamLayer, public NodeRelationAnchorPoint $parentNodeAnchor, public NodeRelationAnchorPoint $childNodeAnchor, - public ContentStreamDbId $contentStreamDbId, public DimensionSpacePoint $dimensionSpacePoint, public string $dimensionSpacePointHash, public int $position, @@ -41,20 +41,20 @@ public function __construct( } public function with( - ?HierarchyRelationDbId $hierarchyRelationDId = null, + ?HierarchyRelationId $hierarchyRelationDId = null, ?NodeRelationAnchorPoint $parentNodeAnchor = null, ?NodeRelationAnchorPoint $childNodeAnchor = null, - ?ContentStreamDbId $contentStreamDbId = null, + ?ContentStreamLayer $contentStreamLayer = null, ?DimensionSpacePoint $dimensionSpacePoint = null, ?string $dimensionSpacePointHash = null, ?int $position = null, ?NodeTags $subtreeTags = null, ): self { return new self( - hierarchyRelationDbId: $hierarchyRelationDId ?? $this->hierarchyRelationDbId, + hierarchyRelationId: $hierarchyRelationDId ?? $this->hierarchyRelationId, + contentStreamLayer: $contentStreamLayer ?? $this->contentStreamLayer, parentNodeAnchor: $parentNodeAnchor ?? $this->parentNodeAnchor, childNodeAnchor: $childNodeAnchor ?? $this->childNodeAnchor, - contentStreamDbId: $contentStreamDbId ?? $this->contentStreamDbId, dimensionSpacePoint: $dimensionSpacePoint ?? $this->dimensionSpacePoint, dimensionSpacePointHash: $dimensionSpacePointHash ?? $this->dimensionSpacePointHash, position: $position ?? $this->position, @@ -74,10 +74,10 @@ public function addToDatabase(Connection $databaseConnection, ContentGraphTableN try { $databaseConnection->insert($tableNames->hierarchyRelation(), [ - 'id' => $this->hierarchyRelationDbId->value, + 'id' => $this->hierarchyRelationId->value, 'parentnodeanchor' => $this->parentNodeAnchor->value, 'childnodeanchor' => $this->childNodeAnchor->value, - 'contentstreamdbid' => $this->contentStreamDbId->value, + 'contentstreamlayer' => $this->contentStreamLayer->value, 'dimensionspacepointhash' => $this->dimensionSpacePointHash, 'position' => $this->position, 'subtreetags' => $subtreeTagsJson, @@ -157,17 +157,17 @@ public function assignNewPosition(int $position, Connection $databaseConnection, */ public function getDatabaseId(): array { - if (!$this->hierarchyRelationDbId->value) { + if (!$this->hierarchyRelationId->value) { throw new \RuntimeException(sprintf('Hierarchy relation was not created in the database and does not have an id: %s', json_encode([ 'parentnodeanchor' => $this->parentNodeAnchor->value, 'childnodeanchor' => $this->childNodeAnchor->value, - 'contentstreamdbid' => $this->contentStreamDbId->value, + 'contentstreamlayer' => $this->contentStreamLayer->value, 'dimensionspacepointhash' => $this->dimensionSpacePointHash ])), 1775979706); } return [ - 'id' => $this->hierarchyRelationDbId->value, - 'contentstreamdbid' => $this->contentStreamDbId->value, + 'id' => $this->hierarchyRelationId->value, + 'contentstreamlayer' => $this->contentStreamLayer->value, ]; } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelationDbId.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelationId.php similarity index 81% rename from Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelationDbId.php rename to Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelationId.php index 7d6f349faeb..eb5ed6dc06d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelationDbId.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelationId.php @@ -7,13 +7,13 @@ /** * @internal */ -class HierarchyRelationDbId +class HierarchyRelationId { private function __construct( public ?int $value ) { if ($value !== null && $value < 0) { - throw new \InvalidArgumentException('A HierarchyRelationDbId cannot be negative, got %d', $value); + throw new \InvalidArgumentException('A HierarchyRelationId cannot be negative, got %d', $value); } } @@ -22,7 +22,7 @@ public static function createAutoIncremented(): self return new self(null); } - public function equals(HierarchyRelationDbId $id): bool + public function equals(HierarchyRelationId $id): bool { return $this->value === $id->value; } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 367246fcf33..e3400f68563 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -19,7 +19,7 @@ use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Query\QueryBuilder; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; @@ -76,7 +76,7 @@ public function __construct( private readonly ContentGraphTableNames $tableNames, public readonly WorkspaceName $workspaceName, public readonly ContentStreamId $contentStreamId, - public readonly ContentStreamDbIds $contentStreamDbIds, + public readonly ContentStreamLayers $contentStreamLayers, ) { $this->nodeQueryBuilder = new NodeQueryBuilder($this->dbal, $this->tableNames); } @@ -98,7 +98,7 @@ public function getSubgraph( return new ContentSubgraph( $this->contentRepositoryId, $this->workspaceName, - $this->contentStreamDbIds, + $this->contentStreamLayers, $dimensionSpacePoint, $visibilityConstraints, $this->dbal, @@ -137,7 +137,7 @@ public function findRootNodeAggregateByType( public function findRootNodeAggregates( FindRootNodeAggregatesFilter $filter, ): NodeAggregates { - $rootNodeAggregateQueryBuilder = $this->nodeQueryBuilder->buildFindRootNodeAggregatesQuery($this->contentStreamDbIds, $filter); + $rootNodeAggregateQueryBuilder = $this->nodeQueryBuilder->buildFindRootNodeAggregatesQuery($this->contentStreamLayers, $filter); return $this->mapQueryBuilderToNodeAggregates($rootNodeAggregateQueryBuilder); } @@ -148,10 +148,10 @@ public function findNodeAggregatesByType( $queryBuilder ->andWhere('n.nodetypename = :nodeTypeName') ->setParameters([ - 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $this->contentStreamLayers->toIntArray(), 'nodeTypeName' => $nodeTypeName->value, ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } @@ -164,9 +164,9 @@ public function findNodeAggregateById( ->orderBy('n.relationanchorpoint', 'DESC') ->setParameters([ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray() + 'contentStreamLayers' => $this->contentStreamLayers->toIntArray() ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); return $this->nodeFactory->mapNodeRowsToNodeAggregate( @@ -184,10 +184,10 @@ public function findNodeAggregatesByIds( ->orderBy('n.relationanchorpoint', 'DESC') ->setParameters([ 'nodeAggregateIds' => $nodeAggregateIds->toStringArray(), - 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray() + 'contentStreamLayers' => $this->contentStreamLayers->toIntArray() ], [ 'nodeAggregateIds' => ArrayParameterType::STRING, - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); @@ -207,9 +207,9 @@ public function findParentNodeAggregates( ->andWhere('cn.nodeaggregateid = :nodeAggregateId') ->setParameters([ 'nodeAggregateId' => $childNodeAggregateId->value, - 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray() + 'contentStreamLayers' => $this->contentStreamLayers->toIntArray() ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); @@ -221,20 +221,20 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate ->select('ch.parentnodeanchor') ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') - ->where('ch.contentstreamdbid = :contentStreamDbIds') + ->where('ch.contentstreamlayer = :contentStreamLayers') ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); $queryBuilderRecursive = $this->createQueryBuilder() ->select('ph.parentnodeanchor') ->from('ancestry', 'ch') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = ch.parentnodeanchor') - ->where('ph.contentstreamdbid = :contentStreamDbIds'); + ->where('ph.contentstreamlayer = :contentStreamLayers'); $queryBuilderCte = $this->createQueryBuilder() ->select('n.nodeAggregateId') ->from('ancestry', 'a') ->innerJoin('a', $this->nodeQueryBuilder->tableNames->node(), 'n', 'n.relationanchorpoint = a.parentnodeanchor') - ->setParameter('contentStreamDbIds', $this->contentStreamDbIds->toIntArray(), ArrayParameterType::STRING) + ->setParameter('contentStreamLayers', $this->contentStreamLayers->toIntArray(), ArrayParameterType::STRING) ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); $nodeAggregateIdRows = $this->fetchCteResults( @@ -250,7 +250,7 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate public function findChildNodeAggregates( NodeAggregateId $parentNodeAggregateId ): NodeAggregates { - $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamDbIds); + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamLayers); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } @@ -265,17 +265,17 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr ->andWhere('cn.origindimensionspacepointhash = :childOriginDimensionSpacePointHash'); $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.contentstreamdbid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->select('n.*, h.contentstreamlayer, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') ->innerJoin('n', $this->nodeQueryBuilder->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') ->setParameters([ - 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $this->contentStreamLayers->toIntArray(), 'childNodeAggregateId' => $childNodeAggregateId->value, 'childOriginDimensionSpacePointHash' => $childOriginDimensionSpacePoint->hash, ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); return $this->nodeFactory->mapNodeRowsToNodeAggregate( @@ -287,7 +287,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr public function findTetheredChildNodeAggregates(NodeAggregateId $parentNodeAggregateId): NodeAggregates { - $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamDbIds) + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamLayers) ->andWhere('cn.classification = :tetheredClassification') ->setParameter('tetheredClassification', NodeAggregateClassification::CLASSIFICATION_TETHERED->value); @@ -298,7 +298,7 @@ public function findChildNodeAggregateByName( NodeAggregateId $parentNodeAggregateId, NodeName $name ): ?NodeAggregate { - $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamDbIds) + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamLayers) ->andWhere('cn.name = :relationName') ->setParameter('relationName', $name->value); @@ -315,19 +315,19 @@ public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeNam ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') - ->andWhere('ph.contentstreamdbid = :contentStreamDbIds') - ->andWhere('h.contentstreamdbid = :contentStreamDbIds') + ->andWhere('ph.contentstreamlayer = :contentStreamLayers') + ->andWhere('h.contentstreamlayer = :contentStreamLayers') ->andWhere('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)') ->andWhere('n.name = :nodeName') ->setParameters([ 'parentNodeAggregateId' => $parentNodeAggregateId->value, 'parentNodeOriginDimensionSpacePointHash' => $parentNodeOriginDimensionSpacePoint->hash, - 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $this->contentStreamLayers->toIntArray(), 'dimensionSpacePointHashes' => $dimensionSpacePointsToCheck->getPointHashes(), 'nodeName' => $nodeName->value ], [ 'dimensionSpacePointHashes' => ArrayParameterType::STRING, - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); $dimensionSpacePoints = []; foreach ($this->fetchRows($queryBuilder) as $hierarchyRelationData) { @@ -340,21 +340,21 @@ public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeNam public function findNodeAggregatesTaggedBy(SubtreeTag $subtreeTag): NodeAggregates { $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.contentstreamdbid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->select('n.*, h.contentstreamlayer, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') // select the subtree tags from tagged (t) h and then join h again to fetch all node rows in that aggregate ->from($this->tableNames->hierarchyRelation(), 'th') ->innerJoin('th', $this->tableNames->hierarchyRelation(), 'h', 'th.childnodeanchor = h.childnodeanchor') ->innerJoin('h', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->where('th.contentstreamdbid = :contentStreamDbIds') + ->where('th.contentstreamlayer = :contentStreamLayers') ->andWhere('JSON_EXTRACT(th.subtreetags, :tagPath) LIKE "true"') - ->andWhere('h.contentstreamdbid = :contentStreamDbIds') + ->andWhere('h.contentstreamlayer = :contentStreamLayers') ->orderBy('n.relationanchorpoint', 'DESC') ->setParameters([ 'tagPath' => '$."' . $subtreeTag->value . '"', - 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray() + 'contentStreamLayers' => $this->contentStreamLayers->toIntArray() ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php deleted file mode 100644 index 3bc4ea9f091..00000000000 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamDbIdFinder.php +++ /dev/null @@ -1,83 +0,0 @@ - - */ - private array $contentStreamIdRuntimeCache = []; - - public function __construct( - private readonly Connection $dbal, - private readonly ContentGraphTableNames $tableNames, - ) { - } - - // todo rename - public function getContentStreamDbId(ContentStreamId $contentStreamId): ContentStreamDbIds - { - $contentStreamDbIds = $this->getFromRuntimeCache($contentStreamId); - if ($contentStreamDbIds === null) { - $this->fillRuntimeCacheFromDatabase(); - $contentStreamDbIds = $this->getFromRuntimeCache($contentStreamId); - // todo reenable runtime cache - $this->contentStreamIdRuntimeCache = []; - } - - if ($contentStreamDbIds === null) { - throw new \RuntimeException(sprintf('A ContentStream with id "%s" was not found in the projection, cannot determine ContentStreamDbId.', $contentStreamId->value), 1769945094); - } - - return $contentStreamDbIds; - } - - private function getFromRuntimeCache(ContentStreamId $contentStreamId): ?ContentStreamDbIds - { - return $this->contentStreamIdRuntimeCache[$contentStreamId->value] ?? null; - } - - private function fillRuntimeCacheFromDatabase(): void - { - $allContentStreamIdsStatement = <<tableNames->contentStreamId()} - SQL; - try { - $allContentStreamIds = $this->dbal->fetchAllAssociative($allContentStreamIdsStatement); - } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load content stream ids from database: %s', $e->getMessage()), 1769945050, $e); - } - $ids = []; - foreach ($allContentStreamIds as $contentStreamIdRow) { - $ids[$contentStreamIdRow['id']][] = $contentStreamIdRow['dbId']; - } - foreach ($ids as $contentStreamId => $dbIds) { - $this->contentStreamIdRuntimeCache[$contentStreamId] = ContentStreamDbIds::fromArray($dbIds); - } - } -} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamLayerFinder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamLayerFinder.php new file mode 100644 index 00000000000..313cbd6d253 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentStreamLayerFinder.php @@ -0,0 +1,47 @@ +tableNames->contentStreamLayer()} + WHERE contentstreamid = :contentStreamId + SQL; + try { + $contentStreamLayers = $this->dbal->fetchFirstColumn($contentStreamLayersStatement, ['contentStreamId' => $contentStreamId->value]); + } catch (DBALException $e) { + throw new \RuntimeException(sprintf('Failed to load content stream ids from database: %s', $e->getMessage()), 1769945050, $e); + } + return ContentStreamLayers::fromArray($contentStreamLayers); + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 504adb8203d..ea42312c17f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -20,7 +20,7 @@ use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; @@ -95,7 +95,7 @@ final class ContentSubgraph implements ContentSubgraphInterface public function __construct( private readonly ContentRepositoryId $contentRepositoryId, private readonly WorkspaceName $workspaceName, - private readonly ContentStreamDbIds $contentStreamDbIds, + private readonly ContentStreamLayers $contentStreamLayers, private readonly DimensionSpacePoint $dimensionSpacePoint, private readonly VisibilityConstraints $visibilityConstraints, private readonly Connection $dbal, @@ -169,7 +169,7 @@ public function countBackReferences(NodeAggregateId $nodeAggregateId, CountBackR public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbIds, $this->dimensionSpacePoint) + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamLayers, $this->dimensionSpacePoint) ->andWhere('n.nodeaggregateid = :nodeAggregateId') ->setParameter('nodeAggregateId', $nodeAggregateId->value); $this->addSubtreeTagConstraints($queryBuilder); @@ -178,7 +178,7 @@ public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node public function findNodesByIds(NodeAggregateIds $nodeAggregateIds): Nodes { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbIds, $this->dimensionSpacePoint) + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamLayers, $this->dimensionSpacePoint) ->andWhere('n.nodeaggregateid in (:nodeAggregateIds)') ->setParameter('nodeAggregateIds', $nodeAggregateIds->toStringArray(), ArrayParameterType::STRING); $this->addSubtreeTagConstraints($queryBuilder); @@ -187,7 +187,7 @@ public function findNodesByIds(NodeAggregateIds $nodeAggregateIds): Nodes public function findRootNodeByType(NodeTypeName $nodeTypeName): ?Node { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbIds, $this->dimensionSpacePoint) + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamLayers, $this->dimensionSpacePoint) ->andWhere('n.nodetypename = :nodeTypeName')->setParameter('nodeTypeName', $nodeTypeName->value) ->andWhere('n.classification = :nodeAggregateClassification')->setParameter('nodeAggregateClassification', NodeAggregateClassification::CLASSIFICATION_ROOT->value); $this->addSubtreeTagConstraints($queryBuilder); @@ -196,7 +196,7 @@ public function findRootNodeByType(NodeTypeName $nodeTypeName): ?Node public function findParentNode(NodeAggregateId $childNodeAggregateId): ?Node { - $queryBuilder = $this->nodeQueryBuilder->buildBasicParentNodeQuery($childNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint); + $queryBuilder = $this->nodeQueryBuilder->buildBasicParentNodeQuery($childNodeAggregateId, $this->contentStreamLayers, $this->dimensionSpacePoint); $this->addSubtreeTagConstraints($queryBuilder, 'ph'); return $this->fetchNode($queryBuilder); } @@ -228,7 +228,7 @@ public function findNodeByAbsolutePath(AbsoluteNodePath $path): ?Node */ private function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNodeAggregateId, NodeName $nodeName): ?Node { - $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint) + $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamLayers, $this->dimensionSpacePoint) ->andWhere('n.name = :edgeName')->setParameter('edgeName', $nodeName->value); $this->addSubtreeTagConstraints($queryBuilder); return $this->fetchNode($queryBuilder); @@ -276,7 +276,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi ->select('n.*, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('h.contentstreamdbid IN (:contentStreamDbIds)') + ->where('h.contentstreamlayer IN (:contentStreamLayers)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial); @@ -286,7 +286,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi ->from('tree', 'p') ->innerJoin('p', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') ->innerJoin('p', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = h.childnodeanchor') - ->where('h.contentstreamdbid IN (:contentStreamDbIds)') + ->where('h.contentstreamlayer IN (:contentStreamLayers)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); if ($filter->maximumLevels !== null) { $queryBuilderRecursive->andWhere('p.level < :maximumLevels')->setParameter('maximumLevels', $filter->maximumLevels); @@ -301,7 +301,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi ->from('tree') ->orderBy('level') ->addOrderBy('position') - ->setParameter('contentStreamDbIds', $this->contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) + ->setParameter('contentStreamLayers', $this->contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER) ->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); @@ -380,7 +380,7 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose ->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(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') - ->andWhere('ph.contentstreamdbid IN (:contentStreamDbIds)') + ->andWhere('ph.contentstreamlayer IN (:contentStreamLayers)') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial, 'ph'); @@ -390,11 +390,11 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose ->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.contentstreamdbid IN (:contentStreamDbIds)') + ->where('h.contentstreamlayer IN (:contentStreamLayers)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamLayers, $this->dimensionSpacePoint); $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'pn'); $nodeRows = $this->fetchCteResults( $queryBuilderInitial, @@ -437,7 +437,7 @@ public function countDescendantNodes(NodeAggregateId $entryNodeAggregateId, Coun public function countNodes(): int { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamDbIds, $this->dimensionSpacePoint, 'n', 'COUNT(*)'); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamLayers, $this->dimensionSpacePoint, 'n', 'COUNT(*)'); try { $result = $this->executeQuery($queryBuilder)->fetchOne(); } catch (DBALException $e) { @@ -483,7 +483,7 @@ private function addSubtreeTagConstraints(QueryBuilder $queryBuilder, string $hi private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, FindChildNodesFilter|CountChildNodesFilter $filter): QueryBuilder { - $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint); + $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamLayers, $this->dimensionSpacePoint); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); } @@ -501,11 +501,11 @@ private function buildReferencesQuery(NodeAggregateId $nodeAggregateId, FindRefe { $subselectParameters = [ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $this->contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $this->dimensionSpacePoint->hash, ]; $subselectTypes = [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]; $subtreeTagConstraints = ''; $i = 0; @@ -524,12 +524,12 @@ private function buildReferencesQuery(NodeAggregateId $nodeAggregateId, FindRefe SELECT relationanchorpoint FROM ' . $this->nodeQueryBuilder->tableNames->node() . ' sn JOIN ' . $this->nodeQueryBuilder->tableNames->hierarchyRelation() . ' sh ON sn.relationanchorpoint = sh.childnodeanchor WHERE sn.nodeaggregateid = :nodeAggregateId - AND sh.contentstreamdbid IN (:contentStreamDbIds) + AND sh.contentstreamlayer IN (:contentStreamLayers) AND sh.dimensionspacepointhash = :dimensionSpacePointHash ' . $subtreeTagConstraints . ' )')->setParameters($subselectParameters, $subselectTypes) ->andWhere('dh.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('dh.contentstreamdbid IN (:contentStreamDbIds)')->setParameter('contentStreamDbIds', $this->contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER); + ->andWhere('dh.contentstreamlayer IN (:contentStreamLayers)')->setParameter('contentStreamLayers', $this->contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER); $this->addSubtreeTagConstraints($queryBuilder, 'dh'); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), "dn"); @@ -568,11 +568,11 @@ private function buildBackreferencesQuery(NodeAggregateId $nodeAggregateId, Find { $subselectParameters = [ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbIds' => $this->contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $this->contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $this->dimensionSpacePoint->hash, ]; $subselectTypes = [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]; $subtreeTagConstraints = ''; $i = 0; @@ -591,12 +591,12 @@ private function buildBackreferencesQuery(NodeAggregateId $nodeAggregateId, Find SELECT nodeaggregateid FROM ' . $this->nodeQueryBuilder->tableNames->node() . ' dn JOIN ' . $this->nodeQueryBuilder->tableNames->hierarchyRelation() . ' dh ON dn.relationanchorpoint = dh.childnodeanchor WHERE dn.nodeaggregateid = :nodeAggregateId - AND dh.contentstreamdbid IN (:contentStreamDbIds) + AND dh.contentstreamlayer IN (:contentStreamLayers) AND dh.dimensionspacepointhash = :dimensionSpacePointHash ' . $subtreeTagConstraints . ' )')->setParameters($subselectParameters, $subselectTypes) ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('sh.contentstreamdbid IN (:contentStreamDbIds)')->setParameter('contentStreamDbIds', $this->contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER); + ->andWhere('sh.contentstreamlayer IN (:contentStreamLayers)')->setParameter('contentStreamLayers', $this->contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER); $this->addSubtreeTagConstraints($queryBuilder, 'sh'); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), "sn"); @@ -633,7 +633,7 @@ private function buildBackreferencesQuery(NodeAggregateId $nodeAggregateId, Find private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, FindPrecedingSiblingNodesFilter|FindSucceedingSiblingNodesFilter $filter): QueryBuilder { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeSiblingsQuery($preceding, $siblingNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeSiblingsQuery($preceding, $siblingNodeAggregateId, $this->contentStreamLayers, $this->dimensionSpacePoint); $this->addSubtreeTagConstraints($queryBuilder); if ($filter->nodeTypes !== null) { @@ -663,9 +663,9 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId ->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.contentstreamdbid IN (:contentStreamDbIds)') + ->where('ch.contentstreamlayer IN (:contentStreamLayers)') ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash') - ->andWhere('ph.contentstreamdbid IN (:contentStreamDbIds)') + ->andWhere('ph.contentstreamlayer IN (:contentStreamLayers)') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial, 'ph'); @@ -676,11 +676,11 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId ->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.contentstreamdbid IN (:contentStreamDbIds)') + ->where('h.contentstreamlayer IN (:contentStreamLayers)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamLayers, $this->dimensionSpacePoint); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'pn'); } @@ -700,9 +700,9 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('n', $this->nodeQueryBuilder->tableNames->node(), 'p', 'p.relationanchorpoint = h.parentnodeanchor') ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') - ->where('h.contentstreamdbid IN (:contentStreamDbIds)') + ->where('h.contentstreamlayer IN (:contentStreamLayers)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') - ->andWhere('ph.contentstreamdbid IN (:contentStreamDbIds)') + ->andWhere('ph.contentstreamlayer IN (:contentStreamLayers)') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('p.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial); @@ -712,11 +712,11 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate ->from('tree', 'pn') ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') - ->where('h.contentstreamdbid IN (:contentStreamDbIds)') + ->where('h.contentstreamlayer IN (:contentStreamLayers)') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamDbIds, $this->dimensionSpacePoint, 'tree', 'n'); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamLayers, $this->dimensionSpacePoint, 'tree', 'n'); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index cebc22c5559..ae295b0ddd6 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -19,10 +19,10 @@ use Doctrine\DBAL\Exception as DBALException; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayer; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelationDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelationId; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentGraph\DoctrineDbalAdapter\HierarchyRelationQueryBuilder; @@ -57,14 +57,14 @@ public function __construct( * correct) */ public function findParentNode( - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $originDimensionSpacePoint, ?DimensionSpacePoint $coveredDimensionSpacePoint = null ): ?NodeRecord { $parentNodeStatement = <<tableNames->node()} p INNER JOIN {$this->tableNames->hierarchyRelation()} ph ON ph.childnodeanchor = p.relationanchorpoint @@ -74,29 +74,29 @@ public function findParentNode( WHERE c.nodeaggregateid = :childNodeAggregateId AND c.origindimensionspacepointhash = :originDimensionSpacePointHash - AND ph.contentstreamdbid IN (:contentStreamDbIds) - AND ch.contentstreamdbid IN (:contentStreamDbIds) + AND ph.contentstreamlayer IN (:contentStreamLayers) + AND ch.contentstreamlayer IN (:contentStreamLayers) AND ph.dimensionspacepointhash = :coveredDimensionSpacePointHash AND ch.dimensionspacepointhash = :coveredDimensionSpacePointHash SQL; try { $nodeRow = $this->dbal->fetchAssociative($parentNodeStatement, [ - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'childNodeAggregateId' => $childNodeAggregateId->value, 'originDimensionSpacePointHash' => $originDimensionSpacePoint->hash, 'coveredDimensionSpacePointHash' => $coveredDimensionSpacePoint->hash ?? $originDimensionSpacePoint->hash ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load parent node for content stream %s, child node aggregate id %s, origin dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $childNodeAggregateId->value, $originDimensionSpacePoint->toJson(), $e->getMessage()), 1716475976, $e); + throw new \RuntimeException(sprintf('Failed to load parent node for content stream %s, child node aggregate id %s, origin dimension space point %s from database: %s', $contentStreamLayers->toDebugString(), $childNodeAggregateId->value, $originDimensionSpacePoint->toJson(), $e->getMessage()), 1716475976, $e); } return $nodeRow ? NodeRecord::fromDatabaseRow($nodeRow) : null; } public function findNodeInAggregate( - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, DimensionSpacePoint $coveredDimensionSpacePoint ): ?NodeRecord { @@ -109,19 +109,19 @@ public function findNodeInAggregate( INNER JOIN {$this->tableNames->dimensionSpacePoints()} dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $nodeRow = $this->dbal->fetchAssociative($nodeInAggregateStatement, [ - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'nodeAggregateId' => $nodeAggregateId->value, 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load node for content stream %s, aggregate id %s and covered dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $coveredDimensionSpacePoint->toJson(), $e->getMessage()), 1716474165, $e); + throw new \RuntimeException(sprintf('Failed to load node for content stream %s, aggregate id %s and covered dimension space point %s from database: %s', $contentStreamLayers->toDebugString(), $nodeAggregateId->value, $coveredDimensionSpacePoint->toJson(), $e->getMessage()), 1716474165, $e); } return $nodeRow ? NodeRecord::fromDatabaseRow($nodeRow) : null; @@ -130,7 +130,7 @@ public function findNodeInAggregate( public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStream( NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $originDimensionSpacePoint, - ContentStreamDbIds $contentStreamDbIds + ContentStreamLayers $contentStreamLayers ): ?NodeRelationAnchorPoint { $relationAnchorPointsStatement = <<dbal->fetchFirstColumn($relationAnchorPointsStatement, [ 'nodeAggregateId' => $nodeAggregateId->value, 'originDimensionSpacePointHash' => $originDimensionSpacePoint->hash, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load node anchor points for content stream %s, node aggregate %s and origin dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $originDimensionSpacePoint->toJson(), $e->getMessage()), 1716474224, $e); + throw new \RuntimeException(sprintf('Failed to load node anchor points for content stream %s, node aggregate %s and origin dimension space point %s from database: %s', $contentStreamLayers->toDebugString(), $nodeAggregateId->value, $originDimensionSpacePoint->toJson(), $e->getMessage()), 1716474224, $e); } if (count($relationAnchorPoints) > 1) { - throw new \RuntimeException(sprintf('More than one node anchor point for content stream: %s, node aggregate id: %s and origin dimension space point: %s – this should not happen and might be a conceptual problem!', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $originDimensionSpacePoint->toJson()), 1716474484); + throw new \RuntimeException(sprintf('More than one node anchor point for content stream: %s, node aggregate id: %s and origin dimension space point: %s – this should not happen and might be a conceptual problem!', $contentStreamLayers->toDebugString(), $nodeAggregateId->value, $originDimensionSpacePoint->toJson()), 1716474484); } return $relationAnchorPoints === [] ? null : NodeRelationAnchorPoint::fromInteger($relationAnchorPoints[0]); } @@ -165,7 +165,7 @@ public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStrea */ public function getAnchorPointsForNodeAggregateInContentStream( NodeAggregateId $nodeAggregateId, - ContentStreamDbIds $contentStreamDbIds + ContentStreamLayers $contentStreamLayers ): iterable { $relationAnchorPointsStatement = <<tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) SQL; try { $relationAnchorPoints = $this->dbal->fetchFirstColumn($relationAnchorPointsStatement, [ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load node anchor points for content stream %s and node aggregate id %s from database: %s', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $e->getMessage()), 1716474706, $e); + throw new \RuntimeException(sprintf('Failed to load node anchor points for content stream %s and node aggregate id %s from database: %s', $contentStreamLayers->toDebugString(), $nodeAggregateId->value, $e->getMessage()), 1716474706, $e); } return array_map(NodeRelationAnchorPoint::fromInteger(...), $relationAnchorPoints); @@ -217,7 +217,7 @@ public function determineHierarchyRelationPosition( ?NodeRelationAnchorPoint $parentAnchorPoint, ?NodeRelationAnchorPoint $childAnchorPoint, ?NodeRelationAnchorPoint $succeedingSiblingAnchorPoint, - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint ): int { if (!$parentAnchorPoint && !$childAnchorPoint) { @@ -234,20 +234,20 @@ public function determineHierarchyRelationPosition( {$this->tableNames->hierarchyRelation()} h WHERE h.childnodeanchor = :succeedingSiblingAnchorPoint - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { /** @var array $succeedingSiblingRelation */ $succeedingSiblingRelation = $this->dbal->fetchAssociative($succeedingSiblingRelationStatement, [ 'succeedingSiblingAnchorPoint' => $succeedingSiblingAnchorPoint->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - 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', $contentStreamDbIds->toDebugString(), $succeedingSiblingAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716474854, $e); + 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', $contentStreamLayers->toDebugString(), $succeedingSiblingAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716474854, $e); } if (!$succeedingSiblingRelation) { @@ -267,21 +267,21 @@ public function determineHierarchyRelationPosition( {$this->tableNames->hierarchyRelation()} h WHERE h.parentnodeanchor = :anchorPoint - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) AND h.dimensionspacepointhash = :dimensionSpacePointHash AND h.position < :position SQL; try { $precedingSiblingData = $this->dbal->fetchAssociative($precedingSiblingStatement, [ 'anchorPoint' => $parentAnchorPoint->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash, 'position' => $succeedingSiblingPosition ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load preceding sibling relations for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716474957, $e); + throw new \RuntimeException(sprintf('Failed to load preceding sibling relations for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamLayers->toDebugString(), $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716474957, $e); } $precedingSiblingPosition = $precedingSiblingData ? ($precedingSiblingData['position'] ?? null) : null; if (!is_null($precedingSiblingPosition)) { @@ -302,20 +302,20 @@ public function determineHierarchyRelationPosition( {$this->tableNames->hierarchyRelation()} h WHERE h.childnodeanchor = :childAnchorPoint - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { /** @var array $childHierarchyRelationData */ $childHierarchyRelationData = $this->dbal->fetchAssociative($childHierarchyRelationStatement, [ 'childAnchorPoint' => $childAnchorPoint->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load child hierarchy relation for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $childAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475001, $e); + throw new \RuntimeException(sprintf('Failed to load child hierarchy relation for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamLayers->toDebugString(), $childAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475001, $e); } $parentAnchorPoint = NodeRelationAnchorPoint::fromInteger( $childHierarchyRelationData['parentnodeanchor'] @@ -328,19 +328,19 @@ public function determineHierarchyRelationPosition( {$this->tableNames->hierarchyRelation()} h WHERE h.parentnodeanchor = :parentAnchorPoint - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $rightmostSucceedingSiblingRelationData = $this->dbal->fetchAssociative($rightmostSucceedingSiblingRelationStatement, [ 'parentAnchorPoint' => $parentAnchorPoint->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to right most succeeding relation for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475046, $e); + throw new \RuntimeException(sprintf('Failed to right most succeeding relation for content stream %s, anchor point %s and dimension space point %s from database: %s', $contentStreamLayers->toDebugString(), $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475046, $e); } if ($rightmostSucceedingSiblingRelationData) { @@ -359,7 +359,7 @@ public function determineHierarchyRelationPosition( */ public function getOutgoingHierarchyRelationsForNodeAndSubgraph( NodeRelationAnchorPoint $parentAnchorPoint, - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint ): array { $outgoingHierarchyRelationsStatement = <<tableNames->hierarchyRelation()} h WHERE h.parentnodeanchor = :parentAnchorPoint - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $rows = $this->dbal->fetchAllAssociative($outgoingHierarchyRelationsStatement, [ 'parentAnchorPoint' => $parentAnchorPoint->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, parent anchor point %s and dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475151, $e); + throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, parent anchor point %s and dimension space point %s from database: %s', $contentStreamLayers->toDebugString(), $parentAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475151, $e); } return array_map($this->mapRawDataToHierarchyRelation(...), $rows); } @@ -391,7 +391,7 @@ public function getOutgoingHierarchyRelationsForNodeAndSubgraph( */ public function getIngoingHierarchyRelationsForNodeAndSubgraph( NodeRelationAnchorPoint $childAnchorPoint, - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint ): array { $ingoingHierarchyRelationsStatement = <<tableNames->hierarchyRelation()} h WHERE h.childnodeanchor = :childAnchorPoint - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $rows = $this->dbal->fetchAllAssociative($ingoingHierarchyRelationsStatement, [ 'childAnchorPoint' => $childAnchorPoint->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, child anchor point %s and dimension space point %s from database: %s', $contentStreamDbIds->toDebugString(), $childAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475151, $e); + throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, child anchor point %s and dimension space point %s from database: %s', $contentStreamLayers->toDebugString(), $childAnchorPoint->value, $dimensionSpacePoint->toJson(), $e->getMessage()), 1716475151, $e); } return array_map($this->mapRawDataToHierarchyRelation(...), $rows); } @@ -423,7 +423,7 @@ public function getIngoingHierarchyRelationsForNodeAndSubgraph( */ public function findIngoingHierarchyRelationsForNode( NodeRelationAnchorPoint $childAnchorPoint, - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, ?DimensionSpacePointSet $restrictToSet = null ): array { $ingoingHierarchyRelationsStatement = <<tableNames->hierarchyRelation()} h WHERE h.childnodeanchor = :childAnchorPoint - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) SQL; $parameters = [ 'childAnchorPoint' => $childAnchorPoint->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray() + 'contentStreamLayers' => $contentStreamLayers->toIntArray() ]; $types = [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]; if ($restrictToSet) { @@ -451,7 +451,7 @@ public function findIngoingHierarchyRelationsForNode( try { $rows = $this->dbal->fetchAllAssociative($ingoingHierarchyRelationsStatement, $parameters, $types); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, child anchor point %s and dimension space points %s from database: %s', $contentStreamDbIds->toDebugString(), $childAnchorPoint->value, $restrictToSet?->toJson() ?? '[any]', $e->getMessage()), 1716476299, $e); + throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, child anchor point %s and dimension space points %s from database: %s', $contentStreamLayers->toDebugString(), $childAnchorPoint->value, $restrictToSet?->toJson() ?? '[any]', $e->getMessage()), 1716476299, $e); } $relations = []; foreach ($rows as $row) { @@ -465,7 +465,7 @@ public function findIngoingHierarchyRelationsForNode( */ public function findOutgoingHierarchyRelationsForNode( NodeRelationAnchorPoint $parentAnchorPoint, - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, ?DimensionSpacePointSet $restrictToSet = null ): array { $outgoingHierarchyRelationsStatement = << $parentAnchorPoint->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray() + 'contentStreamLayers' => $contentStreamLayers->toIntArray() ]; $types = [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]; if ($restrictToSet) { @@ -492,7 +492,7 @@ public function findOutgoingHierarchyRelationsForNode( try { $rows = $this->dbal->fetchAllAssociative($outgoingHierarchyRelationsStatement, $parameters, $types); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, parent anchor point %s and dimension space points %s from database: %s', $contentStreamDbIds->toDebugString(), $parentAnchorPoint->value, $restrictToSet?->toJson() ?? '[any]', $e->getMessage()), 1716476573, $e); + throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, parent anchor point %s and dimension space points %s from database: %s', $contentStreamLayers->toDebugString(), $parentAnchorPoint->value, $restrictToSet?->toJson() ?? '[any]', $e->getMessage()), 1716476573, $e); } $relations = []; foreach ($rows as $row) { @@ -505,7 +505,7 @@ public function findOutgoingHierarchyRelationsForNode( * @return array */ public function findOutgoingHierarchyRelationsForNodeAggregate( - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $dimensionSpacePointSet ): array { @@ -521,14 +521,14 @@ public function findOutgoingHierarchyRelationsForNodeAggregate( try { $rows = $this->dbal->fetchAllAssociative($outgoingHierarchyRelationsStatement, [ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHashes' => $dimensionSpacePointSet->getPointHashes() ], [ 'dimensionSpacePointHashes' => ArrayParameterType::STRING, - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, node aggregate id %s and dimension space points %s from database: %s', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $dimensionSpacePointSet->toJson(), $e->getMessage()), 1716476690, $e); + throw new \RuntimeException(sprintf('Failed to load outgoing hierarchy relations for content stream %s, node aggregate id %s and dimension space points %s from database: %s', $contentStreamLayers->toDebugString(), $nodeAggregateId->value, $dimensionSpacePointSet->toJson(), $e->getMessage()), 1716476690, $e); } return array_map($this->mapRawDataToHierarchyRelation(...), $rows); } @@ -537,7 +537,7 @@ public function findOutgoingHierarchyRelationsForNodeAggregate( * @return array */ public function findIngoingHierarchyRelationsForNodeAggregate( - ContentStreamDbIds $contentStreamDbIds, + ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, ?DimensionSpacePointSet $dimensionSpacePointSet = null ): array { @@ -552,40 +552,40 @@ public function findIngoingHierarchyRelationsForNodeAggregate( SQL; $parameters = [ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), ...($dimensionSpacePointSet !== null ? ['dimensionSpacePointHashes' => $dimensionSpacePointSet->getPointHashes()] : []), ]; $types = [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ...($dimensionSpacePointSet !== null ? ['dimensionSpacePointHashes' => ArrayParameterType::STRING] : []), ]; try { $rows = $this->dbal->fetchAllAssociative($ingoingHierarchyRelationsStatement, $parameters, $types); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, node aggregate id %s and dimension space points %s from database: %s', $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $dimensionSpacePointSet?->toJson() ?? '[any]', $e->getMessage()), 1716476743, $e); + throw new \RuntimeException(sprintf('Failed to load ingoing hierarchy relations for content stream %s, node aggregate id %s and dimension space points %s from database: %s', $contentStreamLayers->toDebugString(), $nodeAggregateId->value, $dimensionSpacePointSet?->toJson() ?? '[any]', $e->getMessage()), 1716476743, $e); } return array_map($this->mapRawDataToHierarchyRelation(...), $rows); } - public function getAllContentStreamDbIdsAnchorPointIsContainedIn( + public function getAllContentStreamLayersAnchorPointIsContainedIn( NodeRelationAnchorPoint $nodeRelationAnchorPoint - ): ContentStreamDbIds { - $contentStreamDbIdsStatement = <<tableNames->hierarchyRelation()} h WHERE h.childnodeanchor = :nodeRelationAnchorPoint SQL; try { - $contentStreamDbIds = $this->dbal->fetchFirstColumn($contentStreamDbIdsStatement, [ + $contentStreamLayers = $this->dbal->fetchFirstColumn($contentStreamLayersStatement, [ 'nodeRelationAnchorPoint' => $nodeRelationAnchorPoint->value, ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load content stream ids for relation anchor point %s from database: %s', $nodeRelationAnchorPoint->value, $e->getMessage()), 1716478504, $e); } - return ContentStreamDbIds::fromArray($contentStreamDbIds); + return ContentStreamLayers::fromArray($contentStreamLayers); } /** @@ -610,10 +610,10 @@ private function mapRawDataToHierarchyRelation(array $rawData): HierarchyRelatio } return new HierarchyRelation( - HierarchyRelationDbId::fromInt((int)$rawData['id']), + HierarchyRelationId::fromInt((int)$rawData['id']), + ContentStreamLayer::fromInt((int)$rawData['contentstreamlayer']), NodeRelationAnchorPoint::fromInteger((int)$rawData['parentnodeanchor']), NodeRelationAnchorPoint::fromInteger((int)$rawData['childnodeanchor']), - ContentStreamDbId::fromInt((int)$rawData['contentstreamdbid']), DimensionSpacePoint::fromJsonString($dimensionSpacePointJson), $rawData['dimensionspacepointhash'], (int)$rawData['position'], diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php index 4704529421f..0dd80321f64 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php @@ -19,11 +19,13 @@ public function selectHierarchyRowsForContentStream(string $whereClause = ''): s return <<tableNames->hierarchyRelation()} as h - INNER JOIN (SELECT id, MAX(contentstreamdbid) as contentstreamdbid + INNER JOIN (SELECT id, MAX(contentstreamlayer) as contentstreamlayer FROM {$this->tableNames->hierarchyRelation()} - WHERE (contentstreamdbid IN (:contentStreamDbIds)) + WHERE (contentstreamlayer IN (:contentStreamLayers)) GROUP BY id - ) AS hIds ON h.contentstreamdbid = hIds.contentstreamdbid AND h.id = hIds.id + ) AS contentStreamLayers + ON h.id = contentStreamLayers.id + AND h.contentstreamlayer = contentStreamLayers.contentstreamlayer {$whereClause} ) SQL; diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index 5fbbafbd02e..0260019d97d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -7,7 +7,7 @@ use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Query\QueryBuilder; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindRootNodeAggregatesFilter; @@ -47,16 +47,16 @@ public function __construct( public function buildBasicNodeAggregateQuery(): QueryBuilder { return $this->createQueryBuilder() - ->select('n.*, h.contentstreamdbid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->select('n.*, h.contentstreamlayer, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'n') ->innerJoin('n', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash'); } - public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamDbIds $contentStreamDbIds): QueryBuilder + public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamLayers $contentStreamLayers): QueryBuilder { return $this->createQueryBuilder() - ->select('cn.*, ch.contentstreamdbid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') + ->select('cn.*, ch.contentstreamlayer, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'pn') ->innerJoin('pn', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->tableNames->dimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') @@ -65,21 +65,21 @@ public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregat ->orderBy('ch.position') ->setParameters([ 'parentNodeAggregateId' => $parentNodeAggregateId->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); } - public function buildFindRootNodeAggregatesQuery(ContentStreamDbIds $contentStreamDbIds, FindRootNodeAggregatesFilter $filter): QueryBuilder + public function buildFindRootNodeAggregatesQuery(ContentStreamLayers $contentStreamLayers, FindRootNodeAggregatesFilter $filter): QueryBuilder { $queryBuilder = $this->buildBasicNodeAggregateQuery() ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') ->setParameters([ - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'rootEdgeParentAnchorId' => NodeRelationAnchorPoint::forRootEdge()->value, ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); if ($filter->nodeTypeName !== null) { @@ -89,17 +89,17 @@ public function buildFindRootNodeAggregatesQuery(ContentStreamDbIds $contentStre return $queryBuilder; } - public function buildBasicNodeQuery(ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint, string $nodeTableAlias = 'n', string $select = 'n.*, h.subtreetags'): QueryBuilder + public function buildBasicNodeQuery(ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint, string $nodeTableAlias = 'n', string $select = 'n.*, h.subtreetags'): QueryBuilder { return $this->createQueryBuilder() ->select($select) ->from($this->tableNames->node(), $nodeTableAlias) ->innerJoin($nodeTableAlias, $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') - ->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) + ->setParameter('contentStreamLayers', $contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER) ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } - public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { return $this->createQueryBuilder() ->select('n.*, h.subtreetags') @@ -107,11 +107,11 @@ public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId ->innerJoin('pn', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) - ->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) + ->setParameter('contentStreamLayers', $contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER) ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } - public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { return $this->createQueryBuilder() ->select('pn.*, ch.subtreetags') @@ -121,11 +121,11 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ->innerJoin('pn', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') ->innerJoin('pn', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) - ->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) + ->setParameter('contentStreamLayers', $contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER) ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } - public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { $sharedSubQuery = $this->createQueryBuilder() ->from($this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'sh') @@ -135,19 +135,19 @@ public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $si $parentNodeAnchorSubQuery = (clone $sharedSubQuery)->select('sh.parentnodeanchor'); $siblingPositionSubQuery = (clone $sharedSubQuery)->select('sh.position'); - return $this->buildBasicNodeQuery($contentStreamDbIds, $dimensionSpacePoint) + return $this->buildBasicNodeQuery($contentStreamLayers, $dimensionSpacePoint) ->andWhere('h.parentnodeanchor = (' . $parentNodeAnchorSubQuery->getSQL() . ')') ->andWhere('n.nodeaggregateid != :siblingNodeAggregateId')->setParameter('siblingNodeAggregateId', $siblingNodeAggregateId->value) ->andWhere('h.position ' . ($preceding ? '<' : '>') . ' (' . $siblingPositionSubQuery->getSQL() . ')') ->orderBy('h.position', $preceding ? 'DESC' : 'ASC'); } - public function buildBasicNodesCteQuery(NodeAggregateId $entryNodeAggregateId, ContentStreamDbIds $contentStreamDbIds, DimensionSpacePoint $dimensionSpacePoint, string $cteName = 'ancestry', string $cteAlias = 'pn'): QueryBuilder + public function buildBasicNodesCteQuery(NodeAggregateId $entryNodeAggregateId, ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint, string $cteName = 'ancestry', string $cteAlias = 'pn'): QueryBuilder { return $this->createQueryBuilder() ->select('*') ->from($cteName, $cteAlias) - ->setParameter('contentStreamDbIds', $contentStreamDbIds->toIntArray(), ArrayParameterType::INTEGER) + ->setParameter('contentStreamLayers', $contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER) ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); } From 303b4e14a01373dae9afcc04b6bfbaafc7155edc Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:30:40 +0200 Subject: [PATCH 06/17] TASK: Refactor `HierarchyRelationStatement` to value object --- .../src/Domain/Repository/ContentGraph.php | 7 +-- .../Repository/ProjectionContentGraph.php | 25 ++++++---- .../src/HierarchyRelationQueryBuilder.php | 33 ------------- .../src/HierarchyRelationStatement.php | 46 +++++++++++++++++++ .../src/NodeQueryBuilder.php | 17 +++---- 5 files changed, 74 insertions(+), 54 deletions(-) delete mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index e3400f68563..9a807ecada3 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -20,6 +20,7 @@ use Doctrine\DBAL\Query\QueryBuilder; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; +use Neos\ContentGraph\DoctrineDbalAdapter\HierarchyRelationStatement; use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; @@ -202,7 +203,7 @@ public function findParentNodeAggregates( NodeAggregateId $childNodeAggregateId ): NodeAggregates { $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() - ->innerJoin('n', $this->nodeQueryBuilder->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('n', HierarchyRelationStatement::for($this->tableNames)->toSql(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->andWhere('cn.nodeaggregateid = :nodeAggregateId') ->setParameters([ @@ -259,7 +260,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr $subQueryBuilder = $this->createQueryBuilder() ->select('pn.nodeaggregateid') ->from($this->nodeQueryBuilder->tableNames->node(), 'pn') - ->innerJoin('pn', $this->nodeQueryBuilder->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :childOriginDimensionSpacePointHash'), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :childOriginDimensionSpacePointHash')->toSql(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('cn.nodeaggregateid = :childNodeAggregateId') ->andWhere('cn.origindimensionspacepointhash = :childOriginDimensionSpacePointHash'); @@ -267,7 +268,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.contentstreamlayer, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', HierarchyRelationStatement::for($this->tableNames)->toSql(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') ->setParameters([ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index ae295b0ddd6..8021a016ce1 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -25,7 +25,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelationId; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; -use Neos\ContentGraph\DoctrineDbalAdapter\HierarchyRelationQueryBuilder; +use Neos\ContentGraph\DoctrineDbalAdapter\HierarchyRelationStatement; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; @@ -41,13 +41,10 @@ */ class ProjectionContentGraph { - private HierarchyRelationQueryBuilder $hierarchyRelationQueryBuilder; - public function __construct( private readonly Connection $dbal, private readonly ContentGraphTableNames $tableNames, ) { - $this->hierarchyRelationQueryBuilder = new HierarchyRelationQueryBuilder($this->tableNames); } /** @@ -132,12 +129,14 @@ public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStrea OriginDimensionSpacePoint $originDimensionSpacePoint, ContentStreamLayers $contentStreamLayers ): ?NodeRelationAnchorPoint { + $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames)->toSql(); + $relationAnchorPointsStatement = <<tableNames->node()} n - INNER JOIN {$this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream()} as h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN {$hierarchyRelationStatement} as h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND n.origindimensionspacepointhash = :originDimensionSpacePointHash @@ -468,11 +467,13 @@ public function findOutgoingHierarchyRelationsForNode( ContentStreamLayers $contentStreamLayers, ?DimensionSpacePointSet $restrictToSet = null ): array { + $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames)->toSql(); + $outgoingHierarchyRelationsStatement = <<hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream()} h + {$hierarchyRelationStatement} h WHERE h.parentnodeanchor = :parentAnchorPoint SQL; @@ -509,11 +510,15 @@ public function findOutgoingHierarchyRelationsForNodeAggregate( NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $dimensionSpacePointSet ): array { + $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames) + ->where('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)') + ->toSql(); + $outgoingHierarchyRelationsStatement = <<hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash IN (:dimensionSpacePointHashes)')} h + {$hierarchyRelationStatement} h INNER JOIN {$this->tableNames->node()} n ON h.parentnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId @@ -541,11 +546,15 @@ public function findIngoingHierarchyRelationsForNodeAggregate( NodeAggregateId $nodeAggregateId, ?DimensionSpacePointSet $dimensionSpacePointSet = null ): array { + $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames) + ->where($dimensionSpacePointSet !== null ? 'h.dimensionspacepointhash IN (:dimensionSpacePointHashes)' : '') + ->toSql(); + $ingoingHierarchyRelationsStatement = <<hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream($dimensionSpacePointSet !== null ? 'WHERE h.dimensionspacepointhash IN (:dimensionSpacePointHashes)' : '')} h + {$hierarchyRelationStatement} h INNER JOIN {$this->tableNames->node()} n ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php deleted file mode 100644 index 0dd80321f64..00000000000 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationQueryBuilder.php +++ /dev/null @@ -1,33 +0,0 @@ -tableNames->hierarchyRelation()} as h - INNER JOIN (SELECT id, MAX(contentstreamlayer) as contentstreamlayer - FROM {$this->tableNames->hierarchyRelation()} - WHERE (contentstreamlayer IN (:contentStreamLayers)) - GROUP BY id - ) AS contentStreamLayers - ON h.id = contentStreamLayers.id - AND h.contentstreamlayer = contentStreamLayers.contentstreamlayer - {$whereClause} - ) - SQL; - } -} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php new file mode 100644 index 00000000000..a2ee95f6f17 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php @@ -0,0 +1,46 @@ +tableNames, + where: $where, + ); + } + + public function toSql(): string + { + $additionalWhereClauses = $this->where !== '' ? " WHERE {$this->where}\n" : ''; + + return <<tableNames->hierarchyRelation()} as h + INNER JOIN (SELECT id, MAX(contentstreamlayer) as contentstreamlayer + FROM {$this->tableNames->hierarchyRelation()} + WHERE (contentstreamlayer IN (:contentStreamLayers)) + GROUP BY id + ) AS activeLayer ON h.contentstreamlayer = activeLayer.contentstreamlayer AND h.id = activeLayer.id + {$additionalWhereClauses}) + SQL; + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index 0260019d97d..189c298d721 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -35,13 +35,10 @@ */ final readonly class NodeQueryBuilder { - public HierarchyRelationQueryBuilder $hierarchyRelationQueryBuilder; - public function __construct( private Connection $connection, public ContentGraphTableNames $tableNames ) { - $this->hierarchyRelationQueryBuilder = new HierarchyRelationQueryBuilder($this->tableNames); } public function buildBasicNodeAggregateQuery(): QueryBuilder @@ -49,7 +46,7 @@ public function buildBasicNodeAggregateQuery(): QueryBuilder return $this->createQueryBuilder() ->select('n.*, h.contentstreamlayer, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'n') - ->innerJoin('n', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', HierarchyRelationStatement::for($this->tableNames)->toSql(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash'); } @@ -58,7 +55,7 @@ public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregat return $this->createQueryBuilder() ->select('cn.*, ch.contentstreamlayer, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'pn') - ->innerJoin('pn', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', HierarchyRelationStatement::for($this->tableNames)->toSql(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->tableNames->dimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') ->innerJoin('ch', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('pn.nodeaggregateid = :parentNodeAggregateId') @@ -94,7 +91,7 @@ public function buildBasicNodeQuery(ContentStreamLayers $contentStreamLayers, Di return $this->createQueryBuilder() ->select($select) ->from($this->tableNames->node(), $nodeTableAlias) - ->innerJoin($nodeTableAlias, $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') + ->innerJoin($nodeTableAlias, HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') ->setParameter('contentStreamLayers', $contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER) ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } @@ -104,7 +101,7 @@ public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId return $this->createQueryBuilder() ->select('n.*, h.subtreetags') ->from($this->tableNames->node(), 'pn') - ->innerJoin('pn', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) ->setParameter('contentStreamLayers', $contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER) @@ -117,9 +114,9 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ->select('pn.*, ch.subtreetags') ->from($this->tableNames->node(), 'pn') // todo we calculate hierarchy rows twice -> optimise - ->innerJoin('pn', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') - ->innerJoin('pn', $this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) ->setParameter('contentStreamLayers', $contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER) ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); @@ -128,7 +125,7 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { $sharedSubQuery = $this->createQueryBuilder() - ->from($this->hierarchyRelationQueryBuilder->selectHierarchyRowsForContentStream('WHERE h.dimensionspacepointhash = :dimensionSpacePointHash'), 'sh') + ->from(HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'sh') ->innerJoin('sh', $this->tableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') ->where('sn.nodeaggregateid = :siblingNodeAggregateId'); From 73782e08c9dd119000fd73e9f218c821c7fe827b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:36:21 +0200 Subject: [PATCH 07/17] TASK: Refactor `HierarchyRelationStatement` to value object --- .../src/HierarchyRelationStatement.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php index a2ee95f6f17..ba6f70a015d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php @@ -35,11 +35,13 @@ public function toSql(): string return <<tableNames->hierarchyRelation()} as h - INNER JOIN (SELECT id, MAX(contentstreamlayer) as contentstreamlayer - FROM {$this->tableNames->hierarchyRelation()} - WHERE (contentstreamlayer IN (:contentStreamLayers)) + INNER JOIN ( + SELECT id, MAX(contentstreamlayer) as contentstreamlayer + FROM {$this->tableNames->hierarchyRelation()} + WHERE (contentstreamlayer IN (:contentStreamLayers)) GROUP BY id - ) AS activeLayer ON h.contentstreamlayer = activeLayer.contentstreamlayer AND h.id = activeLayer.id + ) AS activeLayer + ON h.id = activeLayer.id AND h.contentstreamlayer = activeLayer.contentstreamlayer {$additionalWhereClauses}) SQL; } From c6bad5157701d75a18ca8a97f3e05bc2d171d616 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:13:30 +0200 Subject: [PATCH 08/17] TASK: Refactor ContentStreamLayers::current --- .../DoctrineDbalContentGraphProjection.php | 12 ++++----- .../Domain/Projection/ContentStreamLayers.php | 8 ++---- .../Domain/Projection/Feature/NodeRemoval.php | 4 +-- .../Projection/Feature/NodeVariation.php | 26 +++++++++---------- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index b3b5fbd5d05..f8127d4d5b9 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -760,7 +760,7 @@ private function updateNodeRecordWithCopyOnWrite( callable $operations ): mixed { $contentStreamLayers = $this->projectionContentGraph->getAllContentStreamLayersAnchorPointIsContainedIn($anchorPoint); - if (!$contentStreamLayers->equals($contentStreamLayersWhereWriteOccurs->current())) { + if (!$contentStreamLayers->equals($contentStreamLayersWhereWriteOccurs->getWriteLayer())) { // Copy on Write needed! // Copy on Write is a purely "Content Stream" related concept; // thus we do not care about different DimensionSpacePoints here (but we copy all edges) @@ -775,7 +775,7 @@ private function updateNodeRecordWithCopyOnWrite( // 2) reconnect all edges belonging to this content stream to the new "copied node". // IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges. - if ($contentStreamLayers->contain($contentStreamLayersWhereWriteOccurs->current())) { + if ($contentStreamLayers->contain($contentStreamLayersWhereWriteOccurs->getWriteLayer())) { $updateHierarchyRelationStatement = <<tableNames->hierarchyRelation()} h SET @@ -793,7 +793,7 @@ private function updateNodeRecordWithCopyOnWrite( $this->dbal->executeStatement($updateHierarchyRelationStatement, [ 'newNodeAnchor' => $copiedNode->relationAnchorPoint->value, 'originalNodeAnchor' => $anchorPoint->value, - 'targetContentStreamLayer' => $contentStreamLayersWhereWriteOccurs->current()->value, + 'targetContentStreamLayer' => $contentStreamLayersWhereWriteOccurs->getWriteLayer()->value, ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to update hierarchy relation: %s', $e->getMessage()), 1716486444, $e); @@ -832,7 +832,7 @@ private function updateNodeRecordWithCopyOnWrite( 'newNodeAnchor' => $copiedNode->relationAnchorPoint->value, 'originalNodeAnchor' => $anchorPoint->value, 'contentStreamLayers' => $contentStreamLayersWhereWriteOccurs->toIntArray(), - 'targetContentStreamLayer' => $contentStreamLayersWhereWriteOccurs->current()->value, + 'targetContentStreamLayer' => $contentStreamLayersWhereWriteOccurs->getWriteLayer()->value, ], [ 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); @@ -994,7 +994,7 @@ private function connectHierarchy( $hierarchyRelation = new HierarchyRelation( HierarchyRelationId::createAutoIncremented(), - $contentStreamLayers->current(), + $contentStreamLayers->getWriteLayer(), $parentNodeAnchorPoint, $childNodeAnchorPoint, $dimensionSpacePoint, @@ -1095,7 +1095,7 @@ private function copyHierarchyRelationToDimensionSpacePoint( $inheritedSubtreeTags = NodeTags::create($sourceHierarchyRelation->subtreeTags->withoutInherited()->all(), $parentSubtreeTags->withoutInherited()->all()); $copy = new HierarchyRelation( HierarchyRelationId::createAutoIncremented(), - $contentStreamLayers->current(), + $contentStreamLayers->getWriteLayer(), $newParent, $newChild, $dimensionSpacePoint, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamLayers.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamLayers.php index 4a13a806a1f..500c1b7dec5 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamLayers.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ContentStreamLayers.php @@ -15,7 +15,6 @@ private function __construct( // todo remove all usages // public int $value, - private ContentStreamLayer $max, public array $items ) { } @@ -25,14 +24,11 @@ public static function from(ContentStreamLayer ...$items): self if ($items === []) { throw new \InvalidArgumentException('Db ids must not be empty', 1775819046); } - $max = []; $indexed = []; foreach ($items as $id) { $indexed[$id->value] = $id; - $max[] = $id->value; } return new self( - max: $indexed[max($max)], items: $indexed, ); } @@ -45,9 +41,9 @@ public static function fromArray(array $array): self ); } - public function current(): ContentStreamLayer + public function getWriteLayer(): ContentStreamLayer { - return $this->max; + return $this->items[max(array_keys($this->items))]; } public function equals(ContentStreamLayer $id): bool diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php index 4a9f2e5a552..0906aa665dd 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php @@ -48,7 +48,7 @@ private function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNode $this->removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes($outgoingRelation, $contentStreamLayers); } - if ($contentStreamLayers->current()->equals($ingoingRelation->contentStreamLayer)) { + if ($contentStreamLayers->getWriteLayer()->equals($ingoingRelation->contentStreamLayer)) { $ingoingRelation->removeFromDatabase($this->dbal, $this->tableNames); // remove node itself if it does not have any incoming hierarchy relations anymore // also remove outbound reference relations @@ -102,7 +102,7 @@ private function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNode $this->dbal->executeStatement($copyRemovedHierarchyRelationStatement, [ 'id' => $ingoingRelation->hierarchyRelationId->value, 'contentStreamLayer' => $ingoingRelation->contentStreamLayer->value, - 'targetContentStreamLayer' => $contentStreamLayers->current()->value, + 'targetContentStreamLayer' => $contentStreamLayers->getWriteLayer()->value, ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to copy hierarchy relation: %s', $e->getMessage()), 1775978611, $e); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php index 54ab6774d2d..dbccdba4931 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php @@ -48,7 +48,7 @@ private function createNodeSpecializationVariant(ContentStreamLayers $contentStr $specializationSiblings->toDimensionSpacePointSet() ) as $hierarchyRelation ) { - if ($contentStreamLayers->current()->equals($hierarchyRelation->contentStreamLayer)) { + if ($contentStreamLayers->getWriteLayer()->equals($hierarchyRelation->contentStreamLayer)) { $hierarchyRelation->assignNewChildNode( $specializedNode->relationAnchorPoint, $this->dbal, @@ -57,7 +57,7 @@ private function createNodeSpecializationVariant(ContentStreamLayers $contentStr } else { $copiedHierarchyRelation = $hierarchyRelation->with( childNodeAnchor: $specializedNode->relationAnchorPoint, - contentStreamLayer: $contentStreamLayers->current(), + contentStreamLayer: $contentStreamLayers->getWriteLayer(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, @@ -98,7 +98,7 @@ private function createNodeSpecializationVariant(ContentStreamLayers $contentStr $hierarchyRelation = new HierarchyRelation( HierarchyRelationId::createAutoIncremented(), - $contentStreamLayers->current(), + $contentStreamLayers->getWriteLayer(), $parentNode->relationAnchorPoint, $specializedNode->relationAnchorPoint, $uncoveredDimensionSpacePoint, @@ -123,7 +123,7 @@ private function createNodeSpecializationVariant(ContentStreamLayers $contentStr $specializationSiblings->toDimensionSpacePointSet() ) as $hierarchyRelation ) { - if ($contentStreamLayers->current()->equals($hierarchyRelation->contentStreamLayer)) { + if ($contentStreamLayers->getWriteLayer()->equals($hierarchyRelation->contentStreamLayer)) { $hierarchyRelation->assignNewParentNode( $specializedNode->relationAnchorPoint, null, @@ -133,7 +133,7 @@ private function createNodeSpecializationVariant(ContentStreamLayers $contentStr } else { $copiedHierarchyRelation = $hierarchyRelation->with( parentNodeAnchor: $specializedNode->relationAnchorPoint, - contentStreamLayer: $contentStreamLayers->current(), + contentStreamLayer: $contentStreamLayers->getWriteLayer(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, @@ -182,7 +182,7 @@ public function createNodeGeneralizationVariant(ContentStreamLayers $contentStre $variantSucceedingSiblings->toDimensionSpacePointSet() ) as $existingIngoingHierarchyRelation ) { - if ($contentStreamLayers->current()->equals($existingIngoingHierarchyRelation->contentStreamLayer)) { + if ($contentStreamLayers->getWriteLayer()->equals($existingIngoingHierarchyRelation->contentStreamLayer)) { $existingIngoingHierarchyRelation->assignNewChildNode( $generalizedNode->relationAnchorPoint, $this->dbal, @@ -191,7 +191,7 @@ public function createNodeGeneralizationVariant(ContentStreamLayers $contentStre } else { $copiedHierarchyRelation = $existingIngoingHierarchyRelation->with( childNodeAnchor: $generalizedNode->relationAnchorPoint, - contentStreamLayer: $contentStreamLayers->current(), + contentStreamLayer: $contentStreamLayers->getWriteLayer(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, @@ -212,7 +212,7 @@ public function createNodeGeneralizationVariant(ContentStreamLayers $contentStre $variantSucceedingSiblings->toDimensionSpacePointSet() ) as $existingOutgoingHierarchyRelation ) { - if ($contentStreamLayers->current()->equals($existingOutgoingHierarchyRelation->contentStreamLayer)) { + if ($contentStreamLayers->getWriteLayer()->equals($existingOutgoingHierarchyRelation->contentStreamLayer)) { $existingOutgoingHierarchyRelation->assignNewParentNode( $generalizedNode->relationAnchorPoint, null, @@ -222,7 +222,7 @@ public function createNodeGeneralizationVariant(ContentStreamLayers $contentStre } else { $copiedHierarchyRelation = $existingOutgoingHierarchyRelation->with( parentNodeAnchor: $generalizedNode->relationAnchorPoint, - contentStreamLayer: $contentStreamLayers->current(), + contentStreamLayer: $contentStreamLayers->getWriteLayer(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, @@ -313,7 +313,7 @@ public function createNodePeerVariant(ContentStreamLayers $contentStreamLayers, $peerSucceedingSiblings->toDimensionSpacePointSet() ) as $existingIngoingHierarchyRelation ) { - if ($contentStreamLayers->current()->equals($existingIngoingHierarchyRelation->contentStreamLayer)) { + if ($contentStreamLayers->getWriteLayer()->equals($existingIngoingHierarchyRelation->contentStreamLayer)) { $existingIngoingHierarchyRelation->assignNewChildNode( $peerNode->relationAnchorPoint, $this->dbal, @@ -322,7 +322,7 @@ public function createNodePeerVariant(ContentStreamLayers $contentStreamLayers, } else { $copiedHierarchyRelation = $existingIngoingHierarchyRelation->with( childNodeAnchor: $peerNode->relationAnchorPoint, - contentStreamLayer: $contentStreamLayers->current(), + contentStreamLayer: $contentStreamLayers->getWriteLayer(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, @@ -343,7 +343,7 @@ public function createNodePeerVariant(ContentStreamLayers $contentStreamLayers, $peerSucceedingSiblings->toDimensionSpacePointSet() ) as $existingOutgoingHierarchyRelation ) { - if ($contentStreamLayers->current()->equals($existingOutgoingHierarchyRelation->contentStreamLayer)) { + if ($contentStreamLayers->getWriteLayer()->equals($existingOutgoingHierarchyRelation->contentStreamLayer)) { $existingOutgoingHierarchyRelation->assignNewParentNode( $peerNode->relationAnchorPoint, null, @@ -353,7 +353,7 @@ public function createNodePeerVariant(ContentStreamLayers $contentStreamLayers, } else { $copiedHierarchyRelation = $existingOutgoingHierarchyRelation->with( parentNodeAnchor: $peerNode->relationAnchorPoint, - contentStreamLayer: $contentStreamLayers->current(), + contentStreamLayer: $contentStreamLayers->getWriteLayer(), ); $copiedHierarchyRelation->addToDatabase( $this->dbal, From abb7c6b481f5d8a838b08d6a80711f332c394efb Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:45:07 +0200 Subject: [PATCH 09/17] WIP: SubtreeTagging with sql upsert --- .../Projection/Feature/SubtreeTagging.php | 221 ++++++++++++------ .../Repository/ProjectionContentGraph.php | 7 +- 2 files changed, 149 insertions(+), 79 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 04d8b96218e..ed91f5a67b4 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -6,9 +6,10 @@ use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Exception as DBALException; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbIds; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; +use Neos\ContentGraph\DoctrineDbalAdapter\HierarchyRelationStatement; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; @@ -22,98 +23,163 @@ */ trait SubtreeTagging { - private function addSubtreeTag(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedDimensionSpacePoints, SubtreeTag $tag): void + private function addSubtreeTag(ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedDimensionSpacePoints, SubtreeTag $tag): void { + $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames); + $addTagToDescendantsStatement = <<tableNames->hierarchyRelation()} h + INSERT INTO {$this->tableNames->hierarchyRelation()} ( + id, + parentnodeanchor, + childnodeanchor, + position, + subtreetags, + dimensionspacepointhash, + contentstreamlayer + ) + SELECT + h.id, + h.parentnodeanchor, + h.childnodeanchor, + h.position, + JSON_INSERT(h.subtreetags, :tagPath, null) as subtreetags, + h.dimensionspacepointhash, + :targetContentStreamLayer as contentstreamlayer + FROM {$this->tableNames->hierarchyRelation()} h JOIN ( + -- todo use new id? WITH RECURSIVE cte (id, dsp) AS ( SELECT ch.childnodeanchor, ch.dimensionspacepointhash - FROM {$this->tableNames->hierarchyRelation()} ch + FROM {$hierarchyRelationStatement->where('h.dimensionspacepointhash in (:dimensionSpacePointHashes) AND NOT JSON_CONTAINS_PATH(h.subtreetags, \'one\', :tagPath)')->toSql()} ch INNER JOIN {$this->tableNames->node()} n ON n.relationanchorpoint = ch.parentnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId - AND ch.contentstreamdbid IN (:contentStreamDbIds) - AND ch.dimensionspacepointhash in (:dimensionSpacePointHashes) - AND NOT JSON_CONTAINS_PATH(ch.subtreetags, 'one', :tagPath) UNION ALL SELECT - dh.childnodeanchor, - dh.dimensionspacepointhash + dh.childnodeanchor, dh.dimensionspacepointhash FROM cte - JOIN {$this->tableNames->hierarchyRelation()} dh ON dh.parentnodeanchor = cte.id - AND dh.contentstreamdbid IN (:contentStreamDbIds) - AND dh.dimensionspacepointhash = cte.dsp + JOIN {$hierarchyRelationStatement->toSql()} dh ON dh.parentnodeanchor = cte.id + -- todo why not in where???? or why not to dimensionSpacePointHashes + AND dh.dimensionspacepointhash = cte.dsp WHERE NOT JSON_CONTAINS_PATH(dh.subtreetags, 'one', :tagPath) ) SELECT * FROM cte - ) subquery ON h.dimensionspacepointhash = subquery.dsp + ) subquery + ON h.dimensionspacepointhash = subquery.dsp AND h.childnodeanchor = subquery.id - SET h.subtreetags = JSON_INSERT(h.subtreetags, :tagPath, null) - WHERE h.contentstreamdbid IN (:contentStreamDbIds) + WHERE h.contentstreamlayer IN (:contentStreamLayers) + ON DUPLICATE KEY UPDATE subtreetags = VALUES(subtreetags) SQL; try { $this->dbal->executeStatement($addTagToDescendantsStatement, [ - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'nodeAggregateId' => $nodeAggregateId->value, 'dimensionSpacePointHashes' => $affectedDimensionSpacePoints->getPointHashes(), 'tagPath' => '$."' . $tag->value . '"', + 'targetContentStreamLayer' => $contentStreamLayers->getWriteLayer()->value, ], [ 'dimensionSpacePointHashes' => ArrayParameterType::STRING, - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('1: Failed to add subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716479749, $e); + throw new \RuntimeException(sprintf('1: Failed to add subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamLayers->toDebugString(), $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716479749, $e); } $addTagToNodeStatement = <<tableNames->hierarchyRelation()} h + INSERT INTO {$this->tableNames->hierarchyRelation()} ( + id, + parentnodeanchor, + childnodeanchor, + position, + subtreetags, + dimensionspacepointhash, + contentstreamlayer + ) + SELECT + h.id, + h.parentnodeanchor, + h.childnodeanchor, + h.position, + JSON_SET(h.subtreetags, :tagPath, true) as subtreetags, + h.dimensionspacepointhash, + :targetContentStreamLayer as contentstreamlayer + FROM {$hierarchyRelationStatement->where('h.dimensionspacepointhash in (:dimensionSpacePointHashes)')->toSql()} h INNER JOIN {$this->tableNames->node()} n ON n.relationanchorpoint = h.childnodeanchor - SET h.subtreetags = JSON_SET(h.subtreetags, :tagPath, true) WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamdbid IN (:contentStreamDbIds) - AND h.dimensionspacepointhash in (:dimensionSpacePointHashes) + ON DUPLICATE KEY UPDATE subtreetags = VALUES(subtreetags) SQL; try { $this->dbal->executeStatement($addTagToNodeStatement, [ - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'nodeAggregateId' => $nodeAggregateId->value, 'dimensionSpacePointHashes' => $affectedDimensionSpacePoints->getPointHashes(), 'tagPath' => '$."' . $tag->value . '"', + 'targetContentStreamLayer' => $contentStreamLayers->getWriteLayer()->value, ], [ 'dimensionSpacePointHashes' => ArrayParameterType::STRING, - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('2: Failed to add subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716479840, $e); + throw new \RuntimeException(sprintf('2: Failed to add subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamLayers->toDebugString(), $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716479840, $e); } } - private function removeSubtreeTag(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedDimensionSpacePoints, SubtreeTag $tag): void + private function removeSubtreeTag(ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedDimensionSpacePoints, SubtreeTag $tag): void { + $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames); + $removeTagStatement = <<tableNames->hierarchyRelation()} h + INSERT INTO {$this->tableNames->hierarchyRelation()} ( + id, + parentnodeanchor, + childnodeanchor, + position, + subtreetags, + dimensionspacepointhash, + contentstreamlayer + ) + SELECT + h.id, + h.parentnodeanchor, + h.childnodeanchor, + h.position, + IF( + ( + SELECT containsTag FROM (SELECT + JSON_CONTAINS_PATH(gph.subtreetags, 'one', :tagPath) as containsTag + FROM + {$this->tableNames->hierarchyRelation()} gph + INNER JOIN {$this->tableNames->hierarchyRelation()} ph ON ph.parentnodeanchor = gph.childnodeanchor + INNER JOIN {$this->tableNames->node()} n ON n.relationanchorpoint = ph.childnodeanchor + WHERE + ph.parentnodeanchor = gph.childnodeanchor + AND n.nodeaggregateid = :nodeAggregateId + AND gph.contentstreamlayer IN (:contentStreamLayers) + LIMIT 1) as containsTagSubQuery + ), JSON_SET(subtreetags, :tagPath, null), JSON_REMOVE(subtreetags, :tagPath) + ) as subtreetags, + h.dimensionspacepointhash, + :targetContentStreamLayer as contentstreamlayer + FROM {$this->tableNames->hierarchyRelation()} h JOIN ( + -- todo use new actual id? WITH RECURSIVE cte (id, dsp) AS ( SELECT ph.childnodeanchor, ph.dimensionspacepointhash - FROM {$this->tableNames->hierarchyRelation()} ph + FROM {$hierarchyRelationStatement->where('h.dimensionspacepointhash in (:dimensionSpacePointHashes)')->toSql()} ph INNER JOIN {$this->tableNames->node()} n ON n.relationanchorpoint = ph.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId - AND ph.contentstreamdbid IN (:contentStreamDbIds) - AND ph.dimensionspacepointhash in (:dimensionSpacePointHashes) UNION ALL SELECT dh.childnodeanchor, dh.dimensionspacepointhash FROM cte - JOIN {$this->tableNames->hierarchyRelation()} dh ON dh.parentnodeanchor = cte.id - AND dh.contentstreamdbid IN (:contentStreamDbIds) + JOIN {$hierarchyRelationStatement->toSql()} dh ON dh.parentnodeanchor = cte.id AND dh.dimensionspacepointhash = cte.dsp WHERE JSON_EXTRACT(dh.subtreetags, :tagPath) != TRUE @@ -121,43 +187,55 @@ private function removeSubtreeTag(ContentStreamDbIds $contentStreamDbIds, NodeAg SELECT * FROM cte ) subquery ON h.dimensionspacepointhash = subquery.dsp AND h.childnodeanchor = subquery.id - SET subtreetags = IF( - ( - SELECT containsTag FROM (SELECT - JSON_CONTAINS_PATH(gph.subtreetags, 'one', :tagPath) as containsTag - FROM - {$this->tableNames->hierarchyRelation()} gph - INNER JOIN {$this->tableNames->hierarchyRelation()} ph ON ph.parentnodeanchor = gph.childnodeanchor - INNER JOIN {$this->tableNames->node()} n ON n.relationanchorpoint = ph.childnodeanchor - WHERE - ph.parentnodeanchor = gph.childnodeanchor - AND n.nodeaggregateid = :nodeAggregateId - AND gph.contentstreamdbid IN (:contentStreamDbIds) - LIMIT 1) as containsTagSubQuery - ), JSON_SET(subtreetags, :tagPath, null), JSON_REMOVE(subtreetags, :tagPath) - ) - WHERE contentstreamdbid IN (:contentStreamDbIds) + WHERE contentstreamlayer IN (:contentStreamLayers) + ON DUPLICATE KEY UPDATE subtreetags = VALUES(subtreetags) SQL; try { $this->dbal->executeStatement($removeTagStatement, [ - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'nodeAggregateId' => $nodeAggregateId->value, 'dimensionSpacePointHashes' => $affectedDimensionSpacePoints->getPointHashes(), 'tagPath' => '$."' . $tag->value . '"', + 'targetContentStreamLayer' => $contentStreamLayers->getWriteLayer()->value, ], [ 'dimensionSpacePointHashes' => ArrayParameterType::STRING, - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to remove subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamDbIds->toDebugString(), $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716482293, $e); + throw new \RuntimeException(sprintf('Failed to remove subtree tag %s for content stream %s, node aggregate id %s and dimension space points %s: %s', $tag->value, $contentStreamLayers->toDebugString(), $nodeAggregateId->value, $affectedDimensionSpacePoints->toJson(), $e->getMessage()), 1716482293, $e); } } - private function moveSubtreeTags(ContentStreamDbIds $contentStreamDbIds, NodeAggregateId $newParentNodeAggregateId, DimensionSpacePoint $coveredDimensionSpacePoint): void + private function moveSubtreeTags(ContentStreamLayers $contentStreamLayers, NodeAggregateId $newParentNodeAggregateId, DimensionSpacePoint $coveredDimensionSpacePoint): void { $moveSubtreeTagsStatement = <<tableNames->hierarchyRelation()} h, + INSERT INTO {$this->tableNames->hierarchyRelation()} ( + id, + parentnodeanchor, + childnodeanchor, + position, + subtreetags, + dimensionspacepointhash, + contentstreamlayer + ) + SELECT + h.id, + h.parentnodeanchor, + h.childnodeanchor, + h.position, ( + SELECT + JSON_MERGE_PATCH( + IFNULL(JSON_OBJECTAGG(htk.k, null), '{}'), + JSON_MERGE_PATCH('{}', h.subtreetags) + ) + FROM + JSON_TABLE(r.subtreeTagsToInherit, '\$[*]' COLUMNS (k VARCHAR(36) PATH '\$')) htk + ) as subtreetags, + h.dimensionspacepointhash, + :targetContentStreamLayer as contentstreamlayer + FROM {$this->tableNames->hierarchyRelation()} h + JOIN ( WITH RECURSIVE cte AS ( SELECT JSON_KEYS(th.subtreetags) subtreeTagsToInherit, th.childnodeanchor @@ -166,7 +244,7 @@ private function moveSubtreeTags(ContentStreamDbIds $contentStreamDbIds, NodeAgg INNER JOIN {$this->tableNames->node()} tn ON tn.relationanchorpoint = th.childnodeanchor WHERE tn.nodeaggregateid = :newParentNodeAggregateId - AND th.contentstreamdbid IN (:contentStreamDbIds) + AND th.contentstreamlayer IN (:contentStreamLayers) AND th.dimensionspacepointhash = :dimensionSpacePointHash UNION SELECT @@ -183,64 +261,55 @@ private function moveSubtreeTags(ContentStreamDbIds $contentStreamDbIds, NodeAgg JOIN {$this->tableNames->hierarchyRelation()} dh ON dh.parentnodeanchor = cte.childnodeanchor - AND dh.contentstreamdbid IN (:contentStreamDbIds) + AND dh.contentstreamlayer IN (:contentStreamLayers) AND dh.dimensionspacepointhash = :dimensionSpacePointHash ) SELECT * FROM cte ) AS r - SET h.subtreetags = ( - SELECT - JSON_MERGE_PATCH( - IFNULL(JSON_OBJECTAGG(htk.k, null), '{}'), - JSON_MERGE_PATCH('{}', h.subtreetags) - ) - FROM - JSON_TABLE(r.subtreeTagsToInherit, '\$[*]' COLUMNS (k VARCHAR(36) PATH '\$')) htk - ) WHERE h.childnodeanchor = r.childnodeanchor - AND h.contentstreamdbid IN (:contentStreamDbIds) + AND h.contentstreamlayer IN (:contentStreamLayers) AND h.dimensionspacepointhash = :dimensionSpacePointHash + ON DUPLICATE KEY UPDATE subtreetags = VALUES(subtreetags) SQL; try { // Mysql hack, too eager to optimize https://dev.mysql.com/doc/refman/8.4/en/derived-table-optimization.html $this->dbal->executeQuery('set optimizer_switch="derived_merge=off"'); $this->dbal->executeStatement($moveSubtreeTagsStatement, [ - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'newParentNodeAggregateId' => $newParentNodeAggregateId->value, 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, + 'targetContentStreamLayer' => $contentStreamLayers->getWriteLayer(), ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to move subtree tags for content stream %s, new parent node aggregate id %s and dimension space point %s: %s', $contentStreamDbIds->toDebugString(), $newParentNodeAggregateId->value, $coveredDimensionSpacePoint->toJson(), $e->getMessage()), 1716482574, $e); + throw new \RuntimeException(sprintf('Failed to move subtree tags for content stream %s, new parent node aggregate id %s and dimension space point %s: %s', $contentStreamLayers->toDebugString(), $newParentNodeAggregateId->value, $coveredDimensionSpacePoint->toJson(), $e->getMessage()), 1716482574, $e); } } - private function subtreeTagsForHierarchyRelation(ContentStreamDbIds $contentStreamDbIds, NodeRelationAnchorPoint $parentNodeAnchorPoint, DimensionSpacePoint $dimensionSpacePoint): NodeTags + private function subtreeTagsForHierarchyRelation(ContentStreamLayers $contentStreamLayers, NodeRelationAnchorPoint $parentNodeAnchorPoint, DimensionSpacePoint $dimensionSpacePoint): NodeTags { if ($parentNodeAnchorPoint->equals(NodeRelationAnchorPoint::forRootEdge())) { return NodeTags::createEmpty(); } try { $subtreeTagsJson = $this->dbal->fetchOne(' - SELECT h.subtreetags FROM ' . $this->tableNames->hierarchyRelation() . ' h + SELECT h.subtreetags FROM ' . HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql() . ' h WHERE h.childnodeanchor = :parentNodeAnchorPoint - AND h.contentstreamdbid IN (:contentStreamDbIds) - AND h.dimensionspacepointhash = :dimensionSpacePointHash ', [ 'parentNodeAnchorPoint' => $parentNodeAnchorPoint->value, - 'contentStreamDbIds' => $contentStreamDbIds->toIntArray(), + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash, ], [ - 'contentStreamDbIds' => ArrayParameterType::INTEGER, + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to fetch subtree tags for hierarchy parent anchor point "%s" in content subgraph "%s@%s": %s', $parentNodeAnchorPoint->value, $dimensionSpacePoint->toJson(), $contentStreamDbIds->toDebugString(), $e->getMessage()), 1716478760, $e); + throw new \RuntimeException(sprintf('Failed to fetch subtree tags for hierarchy parent anchor point "%s" in content subgraph "%s@%s": %s', $parentNodeAnchorPoint->value, $dimensionSpacePoint->toJson(), $contentStreamLayers->toDebugString(), $e->getMessage()), 1716478760, $e); } if (!is_string($subtreeTagsJson)) { - throw new \RuntimeException(sprintf('Failed to fetch subtree tags for hierarchy parent anchor point "%s" in content subgraph "%s@%s"', $parentNodeAnchorPoint->value, $dimensionSpacePoint->toJson(), $contentStreamDbIds->value), 1704199847); + throw new \RuntimeException(sprintf('Failed to fetch subtree tags for hierarchy parent anchor point "%s" in content subgraph "%s@%s"', $parentNodeAnchorPoint->value, $dimensionSpacePoint->toJson(), $contentStreamLayers->toDebugString()), 1704199847); } return NodeFactory::extractNodeTagsFromJson($subtreeTagsJson); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index 8021a016ce1..b58a8293a3c 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -41,10 +41,13 @@ */ class ProjectionContentGraph { + private HierarchyRelationStatement $hierarchyRelationStatement; + public function __construct( private readonly Connection $dbal, private readonly ContentGraphTableNames $tableNames, ) { + $this->hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames); } /** @@ -102,12 +105,10 @@ public function findNodeInAggregate( n.*, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM {$this->tableNames->node()} n - INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h ON h.childnodeanchor = n.relationanchorpoint INNER JOIN {$this->tableNames->dimensionSpacePoints()} dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamlayer IN (:contentStreamLayers) - AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $nodeRow = $this->dbal->fetchAssociative($nodeInAggregateStatement, [ From 041f2a85ec40e32eefd91019a71f02dae80dc40b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:35:52 +0200 Subject: [PATCH 10/17] WIP: use HierarchyRelationStatement on all readside queries --- .../DoctrineDbalContentGraphProjection.php | 2 + .../Projection/Feature/SubtreeTagging.php | 26 +++--- .../src/Domain/Repository/ContentGraph.php | 29 +++---- .../src/Domain/Repository/ContentSubgraph.php | 80 +++++++------------ .../Repository/ProjectionContentGraph.php | 28 ++----- .../src/HierarchyRelationStatement.php | 19 ++++- .../src/NodeQueryBuilder.php | 17 ++-- 7 files changed, 88 insertions(+), 113 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index f8127d4d5b9..3cca7c2fe55 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -87,6 +87,7 @@ final class DoctrineDbalContentGraphProjection implements ContentGraphProjection use SubtreeTagging; use Workspace; + private HierarchyRelationStatement $hierarchyRelationStatement; public const RELATION_DEFAULT_OFFSET = 128; @@ -98,6 +99,7 @@ public function __construct( private readonly ContentStreamLayerFinder $contentStreamLayerFinder, private readonly ContentGraphReadModelInterface $contentGraphReadModel ) { + $this->hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames); } public function setUp(): void diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index ed91f5a67b4..88fece13e2f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -25,8 +25,6 @@ trait SubtreeTagging { private function addSubtreeTag(ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedDimensionSpacePoints, SubtreeTag $tag): void { - $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames); - $addTagToDescendantsStatement = <<tableNames->hierarchyRelation()} ( id, @@ -50,7 +48,7 @@ private function addSubtreeTag(ContentStreamLayers $contentStreamLayers, NodeAgg -- todo use new id? WITH RECURSIVE cte (id, dsp) AS ( SELECT ch.childnodeanchor, ch.dimensionspacepointhash - FROM {$hierarchyRelationStatement->where('h.dimensionspacepointhash in (:dimensionSpacePointHashes) AND NOT JSON_CONTAINS_PATH(h.subtreetags, \'one\', :tagPath)')->toSql()} ch + FROM {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash in (:dimensionSpacePointHashes)')->andWhere('NOT JSON_CONTAINS_PATH(h.subtreetags, \'one\', :tagPath)')->toSql()} ch INNER JOIN {$this->tableNames->node()} n ON n.relationanchorpoint = ch.parentnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId @@ -59,7 +57,7 @@ private function addSubtreeTag(ContentStreamLayers $contentStreamLayers, NodeAgg dh.childnodeanchor, dh.dimensionspacepointhash FROM cte - JOIN {$hierarchyRelationStatement->toSql()} dh ON dh.parentnodeanchor = cte.id + JOIN {$this->hierarchyRelationStatement->toSql()} dh ON dh.parentnodeanchor = cte.id -- todo why not in where???? or why not to dimensionSpacePointHashes AND dh.dimensionspacepointhash = cte.dsp WHERE @@ -106,7 +104,7 @@ private function addSubtreeTag(ContentStreamLayers $contentStreamLayers, NodeAgg JSON_SET(h.subtreetags, :tagPath, true) as subtreetags, h.dimensionspacepointhash, :targetContentStreamLayer as contentstreamlayer - FROM {$hierarchyRelationStatement->where('h.dimensionspacepointhash in (:dimensionSpacePointHashes)')->toSql()} h + FROM {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash in (:dimensionSpacePointHashes)')->toSql()} h INNER JOIN {$this->tableNames->node()} n ON n.relationanchorpoint = h.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId @@ -130,8 +128,6 @@ private function addSubtreeTag(ContentStreamLayers $contentStreamLayers, NodeAgg private function removeSubtreeTag(ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $affectedDimensionSpacePoints, SubtreeTag $tag): void { - $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames); - $removeTagStatement = <<tableNames->hierarchyRelation()} ( id, @@ -169,7 +165,7 @@ private function removeSubtreeTag(ContentStreamLayers $contentStreamLayers, Node -- todo use new actual id? WITH RECURSIVE cte (id, dsp) AS ( SELECT ph.childnodeanchor, ph.dimensionspacepointhash - FROM {$hierarchyRelationStatement->where('h.dimensionspacepointhash in (:dimensionSpacePointHashes)')->toSql()} ph + FROM {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash in (:dimensionSpacePointHashes)')->toSql()} ph INNER JOIN {$this->tableNames->node()} n ON n.relationanchorpoint = ph.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId @@ -179,7 +175,7 @@ private function removeSubtreeTag(ContentStreamLayers $contentStreamLayers, Node dh.dimensionspacepointhash FROM cte - JOIN {$hierarchyRelationStatement->toSql()} dh ON dh.parentnodeanchor = cte.id + JOIN {$this->hierarchyRelationStatement->toSql()} dh ON dh.parentnodeanchor = cte.id AND dh.dimensionspacepointhash = cte.dsp WHERE JSON_EXTRACT(dh.subtreetags, :tagPath) != TRUE @@ -293,12 +289,14 @@ private function subtreeTagsForHierarchyRelation(ContentStreamLayers $contentStr if ($parentNodeAnchorPoint->equals(NodeRelationAnchorPoint::forRootEdge())) { return NodeTags::createEmpty(); } + + $subtreeTagsStatement = <<hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h + WHERE h.childnodeanchor = :parentNodeAnchorPoint + SQL; + try { - $subtreeTagsJson = $this->dbal->fetchOne(' - SELECT h.subtreetags FROM ' . HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql() . ' h - WHERE - h.childnodeanchor = :parentNodeAnchorPoint - ', [ + $subtreeTagsJson = $this->dbal->fetchOne($subtreeTagsStatement, [ 'parentNodeAnchorPoint' => $parentNodeAnchorPoint->value, 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 9a807ecada3..75f1e5a9749 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -69,6 +69,8 @@ final class ContentGraph implements ContentGraphInterface { private readonly NodeQueryBuilder $nodeQueryBuilder; + private readonly HierarchyRelationStatement $hierarchyRelationStatement; + public function __construct( private readonly Connection $dbal, private readonly NodeFactory $nodeFactory, @@ -80,6 +82,7 @@ public function __construct( public readonly ContentStreamLayers $contentStreamLayers, ) { $this->nodeQueryBuilder = new NodeQueryBuilder($this->dbal, $this->tableNames); + $this->hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames); } public function getContentRepositoryId(): ContentRepositoryId @@ -203,7 +206,7 @@ public function findParentNodeAggregates( NodeAggregateId $childNodeAggregateId ): NodeAggregates { $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() - ->innerJoin('n', HierarchyRelationStatement::for($this->tableNames)->toSql(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->hierarchyRelationStatement->toSql(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->andWhere('cn.nodeaggregateid = :nodeAggregateId') ->setParameters([ @@ -220,16 +223,14 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate { $queryBuilderInitial = $this->createQueryBuilder() ->select('ch.parentnodeanchor') - ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch') + ->from($this->hierarchyRelationStatement->toSql(), 'ch') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') - ->where('ch.contentstreamlayer = :contentStreamLayers') ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); $queryBuilderRecursive = $this->createQueryBuilder() ->select('ph.parentnodeanchor') ->from('ancestry', 'ch') - ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = ch.parentnodeanchor') - ->where('ph.contentstreamlayer = :contentStreamLayers'); + ->innerJoin('ch', $this->hierarchyRelationStatement->toSql(), 'ph', 'ph.childnodeanchor = ch.parentnodeanchor'); $queryBuilderCte = $this->createQueryBuilder() ->select('n.nodeAggregateId') @@ -260,7 +261,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr $subQueryBuilder = $this->createQueryBuilder() ->select('pn.nodeaggregateid') ->from($this->nodeQueryBuilder->tableNames->node(), 'pn') - ->innerJoin('pn', HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :childOriginDimensionSpacePointHash')->toSql(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :childOriginDimensionSpacePointHash')->toSql(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('cn.nodeaggregateid = :childNodeAggregateId') ->andWhere('cn.origindimensionspacepointhash = :childOriginDimensionSpacePointHash'); @@ -268,7 +269,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.contentstreamlayer, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') - ->innerJoin('n', HierarchyRelationStatement::for($this->tableNames)->toSql(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->hierarchyRelationStatement->toSql(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') ->setParameters([ @@ -310,15 +311,12 @@ public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeNam { $queryBuilder = $this->createQueryBuilder() ->select('dsp.dimensionspacepoint, h.dimensionspacepointhash') - ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h') + ->from($this->hierarchyRelationStatement->where('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)')->toSql(), 'h') ->innerJoin('h', $this->nodeQueryBuilder->tableNames->node(), 'n', 'n.relationanchorpoint = h.parentnodeanchor') ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->hierarchyRelationStatement->toSql(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') - ->andWhere('ph.contentstreamlayer = :contentStreamLayers') - ->andWhere('h.contentstreamlayer = :contentStreamLayers') - ->andWhere('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)') ->andWhere('n.name = :nodeName') ->setParameters([ 'parentNodeAggregateId' => $parentNodeAggregateId->value, @@ -343,13 +341,10 @@ public function findNodeAggregatesTaggedBy(SubtreeTag $subtreeTag): NodeAggregat $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.contentstreamlayer, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') // select the subtree tags from tagged (t) h and then join h again to fetch all node rows in that aggregate - ->from($this->tableNames->hierarchyRelation(), 'th') - ->innerJoin('th', $this->tableNames->hierarchyRelation(), 'h', 'th.childnodeanchor = h.childnodeanchor') + ->from($this->hierarchyRelationStatement->where('JSON_EXTRACT(th.subtreetags, :tagPath) LIKE "true"')->toSql(), 'th') + ->innerJoin('th', $this->hierarchyRelationStatement->toSql(), 'h', 'th.childnodeanchor = h.childnodeanchor') ->innerJoin('h', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->where('th.contentstreamlayer = :contentStreamLayers') - ->andWhere('JSON_EXTRACT(th.subtreetags, :tagPath) LIKE "true"') - ->andWhere('h.contentstreamlayer = :contentStreamLayers') ->orderBy('n.relationanchorpoint', 'DESC') ->setParameters([ 'tagPath' => '$."' . $subtreeTag->value . '"', diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index ea42312c17f..44c66bb63d3 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -21,6 +21,7 @@ use Doctrine\DBAL\Result; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; +use Neos\ContentGraph\DoctrineDbalAdapter\HierarchyRelationStatement; use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; @@ -92,6 +93,8 @@ final class ContentSubgraph implements ContentSubgraphInterface { private readonly NodeQueryBuilder $nodeQueryBuilder; + private readonly HierarchyRelationStatement $hierarchyRelationStatement; + public function __construct( private readonly ContentRepositoryId $contentRepositoryId, private readonly WorkspaceName $workspaceName, @@ -104,6 +107,7 @@ public function __construct( ContentGraphTableNames $tableNames ) { $this->nodeQueryBuilder = new NodeQueryBuilder($this->dbal, $tableNames); + $this->hierarchyRelationStatement = HierarchyRelationStatement::for($tableNames); } public function getContentRepositoryId(): ContentRepositoryId @@ -275,19 +279,15 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi // @see https://mariadb.com/kb/en/library/recursive-common-table-expressions-overview/#cast-to-avoid-data-truncation ->select('n.*, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('h.contentstreamlayer IN (:contentStreamLayers)') - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') - ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); + ->innerJoin('n', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->where('n.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial); $queryBuilderRecursive = $this->createQueryBuilder() ->select('c.*, h.subtreetags, p.nodeaggregateid AS parentNodeAggregateId, p.level + 1 AS level, h.position') ->from('tree', 'p') - ->innerJoin('p', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') - ->innerJoin('p', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = h.childnodeanchor') - ->where('h.contentstreamlayer IN (:contentStreamLayers)') - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); + ->innerJoin('p', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') + ->innerJoin('p', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = h.childnodeanchor'); if ($filter->maximumLevels !== null) { $queryBuilderRecursive->andWhere('p.level < :maximumLevels')->setParameter('maximumLevels', $filter->maximumLevels); } @@ -379,19 +379,15 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose ->select('n.*, ph.subtreetags, 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(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') - ->andWhere('ph.contentstreamlayer IN (:contentStreamLayers)') - ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') - ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); + ->innerJoin('n', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->where('n.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial, 'ph'); $queryBuilderRecursive = $this->createQueryBuilder() ->select('pn.*, h.subtreetags, 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.contentstreamlayer IN (:contentStreamLayers)') - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); + ->innerJoin('pn', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.childnodeanchor = pn.relationanchorpoint'); $this->addSubtreeTagConstraints($queryBuilderRecursive); $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamLayers, $this->dimensionSpacePoint); @@ -517,19 +513,17 @@ private function buildReferencesQuery(NodeAggregateId $nodeAggregateId, FindRefe $queryBuilder = $this->createQueryBuilder() ->select("dn.*, dh.subtreetags, r.name AS referencename, r.properties AS referenceproperties") - ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'dh') + ->from($this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'dh') ->innerJoin('dh', $this->nodeQueryBuilder->tableNames->node(), 'dn', 'dn.relationanchorpoint = dh.childnodeanchor') ->innerJoin('dn', $this->nodeQueryBuilder->tableNames->referenceRelation(), 'r', 'r.destinationnodeaggregateid = dn.nodeaggregateid') ->where('r.nodeanchorpoint = ( SELECT relationanchorpoint FROM ' . $this->nodeQueryBuilder->tableNames->node() . ' sn - JOIN ' . $this->nodeQueryBuilder->tableNames->hierarchyRelation() . ' sh ON sn.relationanchorpoint = sh.childnodeanchor - WHERE sn.nodeaggregateid = :nodeAggregateId - AND sh.contentstreamlayer IN (:contentStreamLayers) - AND sh.dimensionspacepointhash = :dimensionSpacePointHash ' + JOIN ' . $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql() . ' sh ON sn.relationanchorpoint = sh.childnodeanchor + WHERE sn.nodeaggregateid = :nodeAggregateId ' . $subtreeTagConstraints . ' )')->setParameters($subselectParameters, $subselectTypes) - ->andWhere('dh.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('dh.contentstreamlayer IN (:contentStreamLayers)')->setParameter('contentStreamLayers', $this->contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER); + ->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) + ->setParameter('contentStreamLayers', $this->contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER); $this->addSubtreeTagConstraints($queryBuilder, 'dh'); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), "dn"); @@ -584,19 +578,18 @@ private function buildBackreferencesQuery(NodeAggregateId $nodeAggregateId, Find $queryBuilder = $this->createQueryBuilder() ->select("sn.*, sh.subtreetags, r.name AS referencename, r.properties AS referenceproperties") - ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'sh') + ->from($this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'sh') ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') ->innerJoin('sn', $this->nodeQueryBuilder->tableNames->referenceRelation(), 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') + // todo use join to instead of query ->where('r.destinationnodeaggregateid = ( SELECT nodeaggregateid FROM ' . $this->nodeQueryBuilder->tableNames->node() . ' dn - JOIN ' . $this->nodeQueryBuilder->tableNames->hierarchyRelation() . ' dh ON dn.relationanchorpoint = dh.childnodeanchor - WHERE dn.nodeaggregateid = :nodeAggregateId - AND dh.contentstreamlayer IN (:contentStreamLayers) - AND dh.dimensionspacepointhash = :dimensionSpacePointHash ' + JOIN ' . $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql() . ' dh ON dn.relationanchorpoint = dh.childnodeanchor + WHERE dn.nodeaggregateid = :nodeAggregateId ' . $subtreeTagConstraints . ' )')->setParameters($subselectParameters, $subselectTypes) - ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('sh.contentstreamlayer IN (:contentStreamLayers)')->setParameter('contentStreamLayers', $this->contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER); + ->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) + ->setParameter('contentStreamLayers', $this->contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER); $this->addSubtreeTagConstraints($queryBuilder, 'sh'); if ($filter->nodeTypes !== null) { $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), "sn"); @@ -660,14 +653,11 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId ->select('n.*, ph.subtreetags, ph.parentnodeanchor, 0 AS level') ->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('n', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), '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.contentstreamlayer IN (:contentStreamLayers)') - ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash') - ->andWhere('ph.contentstreamlayer IN (:contentStreamLayers)') - ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') + ->innerJoin('n', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); + // TODO subtree tag constraints on hierarchyRelationStatement!! $this->addSubtreeTagConstraints($queryBuilderInitial, 'ph'); $this->addSubtreeTagConstraints($queryBuilderInitial, 'ch'); @@ -675,9 +665,7 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId ->select('pn.*, h.subtreetags, h.parentnodeanchor, ch.level + 1 AS level') ->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.contentstreamlayer IN (:contentStreamLayers)') - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); + ->innerJoin('pn', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.childnodeanchor = pn.relationanchorpoint'); $this->addSubtreeTagConstraints($queryBuilderRecursive); $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamLayers, $this->dimensionSpacePoint); @@ -697,23 +685,17 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate ->select('n.*, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS 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(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('n', $this->nodeQueryBuilder->tableNames->node(), 'p', 'p.relationanchorpoint = h.parentnodeanchor') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') - ->where('h.contentstreamlayer IN (:contentStreamLayers)') - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') - ->andWhere('ph.contentstreamlayer IN (:contentStreamLayers)') - ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') - ->andWhere('p.nodeaggregateid = :entryNodeAggregateId'); + ->innerJoin('n', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') + ->where('p.nodeaggregateid = :entryNodeAggregateId'); $this->addSubtreeTagConstraints($queryBuilderInitial); $queryBuilderRecursive = $this->createQueryBuilder() ->select('cn.*, h.subtreetags, pn.nodeaggregateid AS parentNodeAggregateId, pn.level + 1 AS level, h.position') ->from('tree', 'pn') - ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') - ->where('h.contentstreamlayer IN (:contentStreamLayers)') - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); + ->innerJoin('pn', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor'); $this->addSubtreeTagConstraints($queryBuilderRecursive); $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamLayers, $this->dimensionSpacePoint, 'tree', 'n'); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index b58a8293a3c..0e96f625e1e 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -67,17 +67,13 @@ public function findParentNode( p.*, ph.contentstreamlayer, ph.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM {$this->tableNames->node()} p - INNER JOIN {$this->tableNames->hierarchyRelation()} ph ON ph.childnodeanchor = p.relationanchorpoint - INNER JOIN {$this->tableNames->hierarchyRelation()} ch ON ch.parentnodeanchor = p.relationanchorpoint + INNER JOIN {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :coveredDimensionSpacePointHash')->toSql()} ph ON ph.childnodeanchor = p.relationanchorpoint + INNER JOIN {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :coveredDimensionSpacePointHash')->toSql()} ch ON ch.parentnodeanchor = p.relationanchorpoint INNER JOIN {$this->tableNames->node()} c ON ch.childnodeanchor = c.relationanchorpoint INNER JOIN {$this->tableNames->dimensionSpacePoints()} dsp ON p.origindimensionspacepointhash = dsp.hash WHERE c.nodeaggregateid = :childNodeAggregateId AND c.origindimensionspacepointhash = :originDimensionSpacePointHash - AND ph.contentstreamlayer IN (:contentStreamLayers) - AND ch.contentstreamlayer IN (:contentStreamLayers) - AND ph.dimensionspacepointhash = :coveredDimensionSpacePointHash - AND ch.dimensionspacepointhash = :coveredDimensionSpacePointHash SQL; try { $nodeRow = $this->dbal->fetchAssociative($parentNodeStatement, [ @@ -130,14 +126,12 @@ public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStrea OriginDimensionSpacePoint $originDimensionSpacePoint, ContentStreamLayers $contentStreamLayers ): ?NodeRelationAnchorPoint { - $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames)->toSql(); - $relationAnchorPointsStatement = <<tableNames->node()} n - INNER JOIN {$hierarchyRelationStatement} as h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN {$this->hierarchyRelationStatement->toSql()} ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND n.origindimensionspacepointhash = :originDimensionSpacePointHash @@ -468,13 +462,11 @@ public function findOutgoingHierarchyRelationsForNode( ContentStreamLayers $contentStreamLayers, ?DimensionSpacePointSet $restrictToSet = null ): array { - $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames)->toSql(); - $outgoingHierarchyRelationsStatement = <<hierarchyRelationStatement->toSql()} h WHERE h.parentnodeanchor = :parentAnchorPoint SQL; @@ -511,15 +503,11 @@ public function findOutgoingHierarchyRelationsForNodeAggregate( NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $dimensionSpacePointSet ): array { - $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames) - ->where('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)') - ->toSql(); - $outgoingHierarchyRelationsStatement = <<hierarchyRelationStatement->where('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)')->toSql()} h INNER JOIN {$this->tableNames->node()} n ON h.parentnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId @@ -547,15 +535,11 @@ public function findIngoingHierarchyRelationsForNodeAggregate( NodeAggregateId $nodeAggregateId, ?DimensionSpacePointSet $dimensionSpacePointSet = null ): array { - $hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames) - ->where($dimensionSpacePointSet !== null ? 'h.dimensionspacepointhash IN (:dimensionSpacePointHashes)' : '') - ->toSql(); - $ingoingHierarchyRelationsStatement = <<hierarchyRelationStatement->where($dimensionSpacePointSet !== null ? 'h.dimensionspacepointhash IN (:dimensionSpacePointHashes)' : '')->toSql()} h INNER JOIN {$this->tableNames->node()} n ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php index ba6f70a015d..f8e16348584 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/HierarchyRelationStatement.php @@ -9,28 +9,39 @@ */ final readonly class HierarchyRelationStatement { + /** + * @param array $whereClauses + */ private function __construct( private ContentGraphTableNames $tableNames, - private string $where, + private array $whereClauses ) { } public static function for(ContentGraphTableNames $tableNames): self { - return new self($tableNames, ''); + return new self($tableNames, []); } public function where(string $where): self { return new self( tableNames: $this->tableNames, - where: $where, + whereClauses: $where === '' ? [] : [$where], + ); + } + + public function andWhere(string $where): self + { + return new self( + tableNames: $this->tableNames, + whereClauses: [...$this->whereClauses, ...($where === '' ? [] : [$where])], ); } public function toSql(): string { - $additionalWhereClauses = $this->where !== '' ? " WHERE {$this->where}\n" : ''; + $additionalWhereClauses = $this->whereClauses === [] ? '' : sprintf(" WHERE %s\n", join("\n AND ", $this->whereClauses)); return <<hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames); } public function buildBasicNodeAggregateQuery(): QueryBuilder @@ -46,7 +49,7 @@ public function buildBasicNodeAggregateQuery(): QueryBuilder return $this->createQueryBuilder() ->select('n.*, h.contentstreamlayer, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'n') - ->innerJoin('n', HierarchyRelationStatement::for($this->tableNames)->toSql(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->hierarchyRelationStatement->toSql(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash'); } @@ -55,7 +58,7 @@ public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregat return $this->createQueryBuilder() ->select('cn.*, ch.contentstreamlayer, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'pn') - ->innerJoin('pn', HierarchyRelationStatement::for($this->tableNames)->toSql(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->hierarchyRelationStatement->toSql(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->tableNames->dimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') ->innerJoin('ch', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('pn.nodeaggregateid = :parentNodeAggregateId') @@ -91,7 +94,7 @@ public function buildBasicNodeQuery(ContentStreamLayers $contentStreamLayers, Di return $this->createQueryBuilder() ->select($select) ->from($this->tableNames->node(), $nodeTableAlias) - ->innerJoin($nodeTableAlias, HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') + ->innerJoin($nodeTableAlias, $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') ->setParameter('contentStreamLayers', $contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER) ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } @@ -101,7 +104,7 @@ public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId return $this->createQueryBuilder() ->select('n.*, h.subtreetags') ->from($this->tableNames->node(), 'pn') - ->innerJoin('pn', HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) ->setParameter('contentStreamLayers', $contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER) @@ -114,9 +117,9 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ->select('pn.*, ch.subtreetags') ->from($this->tableNames->node(), 'pn') // todo we calculate hierarchy rows twice -> optimise - ->innerJoin('pn', HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') - ->innerJoin('pn', HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) ->setParameter('contentStreamLayers', $contentStreamLayers->toIntArray(), ArrayParameterType::INTEGER) ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); @@ -125,7 +128,7 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { $sharedSubQuery = $this->createQueryBuilder() - ->from(HierarchyRelationStatement::for($this->tableNames)->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'sh') + ->from($this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql(), 'sh') ->innerJoin('sh', $this->tableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') ->where('sn.nodeaggregateid = :siblingNodeAggregateId'); From 2e61d5771f1018a7a7f23f6b4fbe5f58d349d691 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:43:02 +0200 Subject: [PATCH 11/17] TASK: Copy on write during move --- .../Domain/Projection/Feature/NodeMove.php | 89 ++++++++++++------- .../Projection/Feature/SubtreeTagging.php | 2 +- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php index ddc8fc73db5..2041268b930 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php @@ -4,7 +4,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamDbId; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\ContentStreamLayers; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; @@ -22,34 +22,34 @@ trait NodeMove { use SubtreeTagging; - private function moveNodeAggregate(ContentStreamDbId $contentStreamDbId, NodeAggregateId $nodeAggregateId, ?NodeAggregateId $newParentNodeAggregateId, InterdimensionalSiblings $succeedingSiblingsForCoverage): void + private function moveNodeAggregate(ContentStreamLayers $contentStreamLayers, NodeAggregateId $nodeAggregateId, ?NodeAggregateId $newParentNodeAggregateId, InterdimensionalSiblings $succeedingSiblingsForCoverage): void { foreach ($succeedingSiblingsForCoverage as $succeedingSiblingForCoverage) { $nodeToBeMoved = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamLayers, $nodeAggregateId, $succeedingSiblingForCoverage->dimensionSpacePoint ); if (is_null($nodeToBeMoved)) { - throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because it does not exist', $nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamDbId->value), 1716471638); + throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because it does not exist', $nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamLayers->toDebugString()), 1716471638); } if ($newParentNodeAggregateId) { $this->moveNodeBeneathParent( - $contentStreamDbId, + $contentStreamLayers, $nodeToBeMoved, $newParentNodeAggregateId, $succeedingSiblingForCoverage ); $this->moveSubtreeTags( - $contentStreamDbId, + $contentStreamLayers, $newParentNodeAggregateId, $succeedingSiblingForCoverage->dimensionSpacePoint ); } else { $this->moveNodeBeforeSucceedingSibling( - $contentStreamDbId, + $contentStreamLayers, $nodeToBeMoved, $succeedingSiblingForCoverage, ); @@ -66,14 +66,14 @@ private function moveNodeAggregate(ContentStreamDbId $contentStreamDbId, NodeAgg * The move target is given as $succeedingSiblingNodeMoveTarget. This also specifies the new parent node. */ private function moveNodeBeforeSucceedingSibling( - ContentStreamDbId $contentStreamDbId, + ContentStreamLayers $contentStreamLayers, NodeRecord $nodeToBeMoved, InterdimensionalSibling $succeedingSiblingForCoverage, ): void { // find the single ingoing hierarchy relation which we want to move $ingoingHierarchyRelation = $this->findIngoingHierarchyRelationToBeMoved( $nodeToBeMoved, - $contentStreamDbId, + $contentStreamLayers, $succeedingSiblingForCoverage->dimensionSpacePoint ); @@ -81,12 +81,12 @@ private function moveNodeBeforeSucceedingSibling( if ($succeedingSiblingForCoverage->nodeAggregateId) { // find the new succeeding sibling NodeRecord; We need this record because we'll use its RelationAnchorPoint later. $newSucceedingSibling = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamLayers, $succeedingSiblingForCoverage->nodeAggregateId, $succeedingSiblingForCoverage->dimensionSpacePoint ); if ($newSucceedingSibling === null) { - throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target succeeding sibling node "%s" is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamDbId->value, $succeedingSiblingForCoverage->nodeAggregateId->value), 1716471881); + throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target succeeding sibling node "%s" is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamLayers->value, $succeedingSiblingForCoverage->nodeAggregateId->value), 1716471881); } } @@ -95,16 +95,27 @@ private function moveNodeBeforeSucceedingSibling( $ingoingHierarchyRelation->parentNodeAnchor, null, $newSucceedingSibling?->relationAnchorPoint, - $contentStreamDbId, + $contentStreamLayers, $succeedingSiblingForCoverage->dimensionSpacePoint ); // ...and assign the new position - $ingoingHierarchyRelation->assignNewPosition( - $newPosition, - $this->dbal, - $this->tableNames - ); + if ($contentStreamLayers->getWriteLayer()->equals($ingoingHierarchyRelation->contentStreamLayer)) { + $ingoingHierarchyRelation->assignNewPosition( + $newPosition, + $this->dbal, + $this->tableNames + ); + } else { + $copiedHierarchyRelation = $ingoingHierarchyRelation->with( + contentStreamLayer: $contentStreamLayers->getWriteLayer(), + position: $newPosition, + ); + $copiedHierarchyRelation->addToDatabase( + $this->dbal, + $this->tableNames + ); + } } /** @@ -116,7 +127,7 @@ private function moveNodeBeforeSucceedingSibling( * We always move beneath the parent before the succeeding sibling if given (or to the end) */ private function moveNodeBeneathParent( - ContentStreamDbId $contentStreamDbId, + ContentStreamLayers $contentStreamLayers, NodeRecord $nodeToBeMoved, NodeAggregateId $parentNodeAggregateId, InterdimensionalSibling $succeedingSiblingForCoverage, @@ -124,30 +135,30 @@ private function moveNodeBeneathParent( // find the single ingoing hierarchy relation which we want to move $ingoingHierarchyRelation = $this->findIngoingHierarchyRelationToBeMoved( $nodeToBeMoved, - $contentStreamDbId, + $contentStreamLayers, $succeedingSiblingForCoverage->dimensionSpacePoint ); // find the new parent NodeRecord; We need this record because we'll use its RelationAnchorPoints later. $newParent = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamLayers, $parentNodeAggregateId, $succeedingSiblingForCoverage->dimensionSpacePoint ); if ($newParent === null) { - throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target parent node is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamDbId->value), 1716471955); + throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target parent node is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamLayers->value), 1716471955); } $newSucceedingSibling = null; if ($succeedingSiblingForCoverage->nodeAggregateId) { // find the new succeeding sibling NodeRecord; We need this record because we'll use its RelationAnchorPoint later. $newSucceedingSibling = $this->projectionContentGraph->findNodeInAggregate( - $contentStreamDbId, + $contentStreamLayers, $succeedingSiblingForCoverage->nodeAggregateId, $succeedingSiblingForCoverage->dimensionSpacePoint ); if ($newSucceedingSibling === null) { - throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target succeeding sibling node is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamDbId->value), 1716471995); + throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target succeeding sibling node is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamLayers->value), 1716471995); } } @@ -156,17 +167,29 @@ private function moveNodeBeneathParent( $newParent->relationAnchorPoint, null, $newSucceedingSibling?->relationAnchorPoint, - $contentStreamDbId, + $contentStreamLayers, $succeedingSiblingForCoverage->dimensionSpacePoint ); // this is the actual move - $ingoingHierarchyRelation->assignNewParentNode( - $newParent->relationAnchorPoint, - $newPosition, - $this->dbal, - $this->tableNames - ); + if ($contentStreamLayers->getWriteLayer()->equals($ingoingHierarchyRelation->contentStreamLayer)) { + $ingoingHierarchyRelation->assignNewParentNode( + $newParent->relationAnchorPoint, + $newPosition, + $this->dbal, + $this->tableNames + ); + } else { + $copiedHierarchyRelation = $ingoingHierarchyRelation->with( + parentNodeAnchor: $newParent->relationAnchorPoint, + contentStreamLayer: $contentStreamLayers->getWriteLayer(), + position: $newPosition, + ); + $copiedHierarchyRelation->addToDatabase( + $this->dbal, + $this->tableNames + ); + } } /** @@ -174,19 +197,19 @@ private function moveNodeBeneathParent( */ private function findIngoingHierarchyRelationToBeMoved( NodeRecord $nodeToBeMoved, - ContentStreamDbId $contentStreamDbId, + ContentStreamLayers $contentStreamLayers, DimensionSpacePoint $coveredDimensionSpacePointWhereMoveShouldHappen ): HierarchyRelation { $restrictToSet = DimensionSpacePointSet::fromArray([$coveredDimensionSpacePointWhereMoveShouldHappen]); $ingoingHierarchyRelations = $this->projectionContentGraph->findIngoingHierarchyRelationsForNode( $nodeToBeMoved->relationAnchorPoint, - $contentStreamDbId, + $contentStreamLayers, $restrictToSet, ); if (count($ingoingHierarchyRelations) !== 1) { // there should always be exactly one incoming relation in the given DimensionSpacePoint; everything // else would be a totally wrong behavior of findIngoingHierarchyRelationsForNode(). - throw new \RuntimeException(sprintf('Failed move node "%s" in sub graph %s@%s because ingoing source hierarchy relation is missing', $nodeToBeMoved->nodeAggregateId->value, $restrictToSet->toJson(), $contentStreamDbId->value), 1716472138); + throw new \RuntimeException(sprintf('Failed move node "%s" in sub graph %s@%s because ingoing source hierarchy relation is missing', $nodeToBeMoved->nodeAggregateId->value, $restrictToSet->toJson(), $contentStreamLayers->value), 1716472138); } return reset($ingoingHierarchyRelations); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 88fece13e2f..3a987faa133 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -275,7 +275,7 @@ private function moveSubtreeTags(ContentStreamLayers $contentStreamLayers, NodeA 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'newParentNodeAggregateId' => $newParentNodeAggregateId->value, 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, - 'targetContentStreamLayer' => $contentStreamLayers->getWriteLayer(), + 'targetContentStreamLayer' => $contentStreamLayers->getWriteLayer()->value, ], [ 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); From e83a76af6e314717a3b07b816df4ffaefb11f6b1 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:56:18 +0200 Subject: [PATCH 12/17] PATCH: Add missing alias --- .../src/Domain/Repository/ProjectionContentGraph.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index 0e96f625e1e..47658b25539 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -131,7 +131,7 @@ public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStrea DISTINCT n.relationanchorpoint FROM {$this->tableNames->node()} n - INNER JOIN {$this->hierarchyRelationStatement->toSql()} ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN {$this->hierarchyRelationStatement->toSql()} as h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND n.origindimensionspacepointhash = :originDimensionSpacePointHash From 86530220ded33d70a71af6fe847a8eaab1b4702e Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:58:32 +0200 Subject: [PATCH 13/17] PATCH: Adjust remaining cases in ProjectionContentGraph to content stream layers --- .../Domain/Projection/Feature/NodeMove.php | 8 ++--- .../Repository/ProjectionContentGraph.php | 32 ++++++------------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php index 2041268b930..65cc6f3e597 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php @@ -86,7 +86,7 @@ private function moveNodeBeforeSucceedingSibling( $succeedingSiblingForCoverage->dimensionSpacePoint ); if ($newSucceedingSibling === null) { - throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target succeeding sibling node "%s" is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamLayers->value, $succeedingSiblingForCoverage->nodeAggregateId->value), 1716471881); + throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target succeeding sibling node "%s" is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamLayers->toDebugString(), $succeedingSiblingForCoverage->nodeAggregateId->value), 1716471881); } } @@ -146,7 +146,7 @@ private function moveNodeBeneathParent( $succeedingSiblingForCoverage->dimensionSpacePoint ); if ($newParent === null) { - throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target parent node is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamLayers->value), 1716471955); + throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target parent node is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamLayers->toDebugString()), 1716471955); } $newSucceedingSibling = null; @@ -158,7 +158,7 @@ private function moveNodeBeneathParent( $succeedingSiblingForCoverage->dimensionSpacePoint ); if ($newSucceedingSibling === null) { - throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target succeeding sibling node is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamLayers->value), 1716471995); + throw new \RuntimeException(sprintf('Failed to move node "%s" in sub graph %s@%s because target succeeding sibling node is missing', $nodeToBeMoved->nodeAggregateId->value, $succeedingSiblingForCoverage->dimensionSpacePoint->toJson(), $contentStreamLayers->toDebugString()), 1716471995); } } @@ -209,7 +209,7 @@ private function findIngoingHierarchyRelationToBeMoved( if (count($ingoingHierarchyRelations) !== 1) { // there should always be exactly one incoming relation in the given DimensionSpacePoint; everything // else would be a totally wrong behavior of findIngoingHierarchyRelationsForNode(). - throw new \RuntimeException(sprintf('Failed move node "%s" in sub graph %s@%s because ingoing source hierarchy relation is missing', $nodeToBeMoved->nodeAggregateId->value, $restrictToSet->toJson(), $contentStreamLayers->value), 1716472138); + throw new \RuntimeException(sprintf('Failed move node "%s" in sub graph %s@%s because ingoing source hierarchy relation is missing', $nodeToBeMoved->nodeAggregateId->value, $restrictToSet->toJson(), $contentStreamLayers->toDebugString()), 1716472138); } return reset($ingoingHierarchyRelations); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index 47658b25539..47eec2334e7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -131,7 +131,7 @@ public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStrea DISTINCT n.relationanchorpoint FROM {$this->tableNames->node()} n - INNER JOIN {$this->hierarchyRelationStatement->toSql()} as h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN {$this->hierarchyRelationStatement->toSql()} AS h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND n.origindimensionspacepointhash = :originDimensionSpacePointHash @@ -166,10 +166,9 @@ public function getAnchorPointsForNodeAggregateInContentStream( DISTINCT n.relationanchorpoint FROM {$this->tableNames->node()} n - INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN {$this->hierarchyRelationStatement->toSql()} h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamlayer IN (:contentStreamLayers) SQL; try { $relationAnchorPoints = $this->dbal->fetchFirstColumn($relationAnchorPointsStatement, [ @@ -225,11 +224,9 @@ public function determineHierarchyRelationPosition( SELECT h.* FROM - {$this->tableNames->hierarchyRelation()} h + {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h WHERE h.childnodeanchor = :succeedingSiblingAnchorPoint - AND h.contentstreamlayer IN (:contentStreamLayers) - AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { /** @var array $succeedingSiblingRelation */ @@ -258,11 +255,9 @@ public function determineHierarchyRelationPosition( SELECT MAX(h.position) AS position FROM - {$this->tableNames->hierarchyRelation()} h + {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h WHERE h.parentnodeanchor = :anchorPoint - AND h.contentstreamlayer IN (:contentStreamLayers) - AND h.dimensionspacepointhash = :dimensionSpacePointHash AND h.position < :position SQL; try { @@ -293,11 +288,9 @@ public function determineHierarchyRelationPosition( SELECT h.parentnodeanchor FROM - {$this->tableNames->hierarchyRelation()} h + {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h WHERE h.childnodeanchor = :childAnchorPoint - AND h.contentstreamlayer IN (:contentStreamLayers) - AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { /** @var array $childHierarchyRelationData */ @@ -319,11 +312,9 @@ public function determineHierarchyRelationPosition( SELECT MAX(h.position) AS position FROM - {$this->tableNames->hierarchyRelation()} h + {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h WHERE h.parentnodeanchor = :parentAnchorPoint - AND h.contentstreamlayer IN (:contentStreamLayers) - AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $rightmostSucceedingSiblingRelationData = $this->dbal->fetchAssociative($rightmostSucceedingSiblingRelationStatement, [ @@ -360,11 +351,9 @@ public function getOutgoingHierarchyRelationsForNodeAndSubgraph( SELECT h.* FROM - {$this->tableNames->hierarchyRelation()} h + {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h WHERE h.parentnodeanchor = :parentAnchorPoint - AND h.contentstreamlayer IN (:contentStreamLayers) - AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $rows = $this->dbal->fetchAllAssociative($outgoingHierarchyRelationsStatement, [ @@ -392,11 +381,9 @@ public function getIngoingHierarchyRelationsForNodeAndSubgraph( SELECT h.* FROM - {$this->tableNames->hierarchyRelation()} h + {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h WHERE h.childnodeanchor = :childAnchorPoint - AND h.contentstreamlayer IN (:contentStreamLayers) - AND h.dimensionspacepointhash = :dimensionSpacePointHash SQL; try { $rows = $this->dbal->fetchAllAssociative($ingoingHierarchyRelationsStatement, [ @@ -424,10 +411,9 @@ public function findIngoingHierarchyRelationsForNode( SELECT h.* FROM - {$this->tableNames->hierarchyRelation()} h + {$this->hierarchyRelationStatement->toSql()} h WHERE h.childnodeanchor = :childAnchorPoint - AND h.contentstreamlayer IN (:contentStreamLayers) SQL; $parameters = [ 'childAnchorPoint' => $childAnchorPoint->value, From dad6febae83f68d5bc8262eebb19a1d5427bf078 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:07:59 +0200 Subject: [PATCH 14/17] PATCH: Re-enable reference copying on node copy on write as per Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/NodeReferencesOnForkContentStream.feature --- .../src/DoctrineDbalContentGraphProjection.php | 10 +++++----- .../NodeReferencesOnForkContentStream.feature | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 3cca7c2fe55..81fcba7b52a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -778,6 +778,7 @@ private function updateNodeRecordWithCopyOnWrite( // IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges. if ($contentStreamLayers->contain($contentStreamLayersWhereWriteOccurs->getWriteLayer())) { + // todo make upsert $updateHierarchyRelationStatement = <<tableNames->hierarchyRelation()} h SET @@ -846,11 +847,10 @@ private function updateNodeRecordWithCopyOnWrite( // reference relation rows need to be copied as well! - // todo - // $this->copyReferenceRelations( - // $anchorPoint, - // $copiedNode->relationAnchorPoint - // ); + $this->copyReferenceRelations( + $anchorPoint, + $copiedNode->relationAnchorPoint + ); return $result; } diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/NodeReferencesOnForkContentStream.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/NodeReferencesOnForkContentStream.feature index 7049a063589..e8074cb2647 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/NodeReferencesOnForkContentStream.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/NodeReferencesOnForkContentStream.feature @@ -76,7 +76,7 @@ Feature: On forking a content stream, node references should be copied as well. And the command SetNodeProperties is executed with payload: | Key | Value | | nodeAggregateId | "source-nodandaise" | - | propertyValues | {"text": "Modified in live workspace"} | + | propertyValues | {"text": "Modified in user workspace"} | Then I expect node aggregate identifier "source-nodandaise" to lead to node user-cs-identifier;source-nodandaise;{"language": "de"} And I expect this node to have the following references: | Name | Node | Properties | From 90d28d705594adcade18b5df8398975c69758c19 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:39:22 +0200 Subject: [PATCH 15/17] TASK: Implement dimension space point migration events with copy on write (move dsp will be a partial fork:O) --- .../DoctrineDbalContentGraphProjection.php | 165 ++++++++---------- 1 file changed, 77 insertions(+), 88 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 81fcba7b52a..fee6e8dfd2a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -309,30 +309,31 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded // 1) hierarchy relations $insertHierarchyRelationsStatement = <<tableNames->hierarchyRelation()} ( + contentstreamlayer, parentnodeanchor, childnodeanchor, position, subtreetags, - dimensionspacepointhash, - contentstreamlayer + dimensionspacepointhash ) SELECT + :targetContentStreamLayer as contentstreamlayer, h.parentnodeanchor, h.childnodeanchor, h.position, h.subtreetags, - :newDimensionSpacePointHash AS dimensionspacepointhash, - h.contentstreamlayer + :newDimensionSpacePointHash AS dimensionspacepointhash FROM - {$this->tableNames->hierarchyRelation()} h - WHERE h.contentstreamlayer IN (:contentStreamLayers) - AND h.dimensionspacepointhash = :sourceDimensionSpacePointHash + {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :sourceDimensionSpacePointHash')->toSql()} h SQL; try { $this->dbal->executeStatement($insertHierarchyRelationsStatement, [ - 'contentStreamLayer' => $this->getContentStreamLayers($event)->value, + 'contentStreamLayers' => $this->getContentStreamLayers($event)->toIntArray(), 'sourceDimensionSpacePointHash' => $event->source->hash, 'newDimensionSpacePointHash' => $event->target->hash, + 'targetContentStreamLayer' => $this->getContentStreamLayers($event)->getWriteLayer()->value, + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER, ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to insert hierarchy relations: %s', $e->getMessage()), 1716490758, $e); @@ -350,10 +351,8 @@ private function whenDimensionSpacePointWasMoved(DimensionSpacePointWasMoved $ev $selectRelationsStatement = <<tableNames->node()} n - INNER JOIN {$this->tableNames->hierarchyRelation()} h + INNER JOIN {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h ON h.childnodeanchor = n.relationanchorpoint - AND h.contentstreamlayer IN (:contentStreamLayers) - AND h.dimensionspacepointhash = :dimensionSpacePointHash -- find only nodes which have their ORIGIN at the source DimensionSpacePoint, -- as we need to rewrite these origins (using copy on write) AND n.origindimensionspacepointhash = :dimensionSpacePointHash @@ -362,7 +361,9 @@ private function whenDimensionSpacePointWasMoved(DimensionSpacePointWasMoved $ev try { $relationAnchorPoints = $this->dbal->fetchFirstColumn($selectRelationsStatement, [ 'dimensionSpacePointHash' => $event->source->hash, - 'contentStreamLayer' => $this->getContentStreamLayers($event)->value + 'contentStreamLayers' => $this->getContentStreamLayers($event)->toIntArray(), + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load relation anchor points: %s', $e->getMessage()), 1716489628, $e); @@ -380,18 +381,35 @@ function (NodeRecord $nodeRecord) use ($event) { // 2) hierarchy relations $updateHierarchyRelationsStatement = <<tableNames->hierarchyRelation()} h - SET - h.dimensionspacepointhash = :newDimensionSpacePointHash - WHERE - h.dimensionspacepointhash = :originalDimensionSpacePointHash - AND h.contentstreamlayer IN (:contentStreamLayers) + INSERT INTO {$this->tableNames->hierarchyRelation()} + ( + id, + contentstreamlayer, + parentnodeanchor, + childnodeanchor, + position, + subtreetags, + dimensionspacepointhash + ) + SELECT + h.id, + :targetContentStreamLayer as contentstreamlayer, + h.parentnodeanchor, + h.childnodeanchor, + h.position, + h.subtreetags, + :newDimensionSpacePointHash AS dimensionspacepointhash + FROM {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :originalDimensionSpacePointHash')->toSql()} AS h + ON DUPLICATE KEY UPDATE dimensionspacepointhash = VALUES(dimensionspacepointhash) SQL; try { $this->dbal->executeStatement($updateHierarchyRelationsStatement, [ 'originalDimensionSpacePointHash' => $event->source->hash, 'newDimensionSpacePointHash' => $event->target->hash, - 'contentStreamLayer' => $this->getContentStreamLayers($event)->value, + 'contentStreamLayers' => $this->getContentStreamLayers($event)->toIntArray(), + 'targetContentStreamLayer' => $this->getContentStreamLayers($event)->getWriteLayer()->value, + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to update hierarchy relations: %s', $e->getMessage()), 1716489951, $e); @@ -761,8 +779,8 @@ private function updateNodeRecordWithCopyOnWrite( NodeRelationAnchorPoint $anchorPoint, callable $operations ): mixed { - $contentStreamLayers = $this->projectionContentGraph->getAllContentStreamLayersAnchorPointIsContainedIn($anchorPoint); - if (!$contentStreamLayers->equals($contentStreamLayersWhereWriteOccurs->getWriteLayer())) { + $contentStreamLayersWithMaterializedNode = $this->projectionContentGraph->getAllContentStreamLayersAnchorPointIsContainedIn($anchorPoint); + if (!$contentStreamLayersWithMaterializedNode->equals($contentStreamLayersWhereWriteOccurs->getWriteLayer())) { // Copy on Write needed! // Copy on Write is a purely "Content Stream" related concept; // thus we do not care about different DimensionSpacePoints here (but we copy all edges) @@ -776,76 +794,47 @@ private function updateNodeRecordWithCopyOnWrite( // 2) reconnect all edges belonging to this content stream to the new "copied node". // IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges. + $copyHierarchyRelationStatement = <<tableNames->hierarchyRelation()} ( + id, + parentnodeanchor, + childnodeanchor, + position, + subtreetags, + dimensionspacepointhash, + contentstreamlayer + ) + SELECT + h.id, + -- if our (copied) node is the parent, we update h.parentNodeAnchor + IF(h.parentnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.parentnodeanchor) as parentnodeanchor, + -- if our (copied) node is the child, we update h.childNodeAnchor + IF(h.childnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.childnodeanchor) as childnodeanchor, + h.position, + h.subtreetags, + h.dimensionspacepointhash, + :targetContentStreamLayer as contentstreamlayer + FROM + {$this->tableNames->hierarchyRelation()} h + WHERE + :originalNodeAnchor IN (h.childnodeanchor, h.parentnodeanchor) + AND h.contentstreamlayer IN (:contentStreamLayers) + ON DUPLICATE KEY UPDATE parentnodeanchor = VALUES(parentnodeanchor), childnodeanchor = VALUES(childnodeanchor) + SQL; - if ($contentStreamLayers->contain($contentStreamLayersWhereWriteOccurs->getWriteLayer())) { - // todo make upsert - $updateHierarchyRelationStatement = <<tableNames->hierarchyRelation()} h - SET - -- if our (copied) node is the child, we update h.childNodeAnchor - h.childnodeanchor = IF(h.childnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.childnodeanchor), - - -- if our (copied) node is the parent, we update h.parentNodeAnchor - h.parentnodeanchor = IF(h.parentnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.parentnodeanchor) - WHERE - :originalNodeAnchor IN (h.childnodeanchor, h.parentnodeanchor) - AND h.contentstreamlayer = :targetContentStreamLayer - SQL; - - try { - $this->dbal->executeStatement($updateHierarchyRelationStatement, [ - 'newNodeAnchor' => $copiedNode->relationAnchorPoint->value, - 'originalNodeAnchor' => $anchorPoint->value, - 'targetContentStreamLayer' => $contentStreamLayersWhereWriteOccurs->getWriteLayer()->value, - ]); - } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to update hierarchy relation: %s', $e->getMessage()), 1716486444, $e); - } - } else { - // todo is this correct - $copyHierarchyRelationStatement = <<tableNames->hierarchyRelation()} ( - id, - parentnodeanchor, - childnodeanchor, - position, - subtreetags, - dimensionspacepointhash, - contentstreamlayer - ) - SELECT - h.id, - -- if our (copied) node is the parent, we update h.parentNodeAnchor - IF(h.parentnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.parentnodeanchor) as parentnodeanchor, - -- if our (copied) node is the child, we update h.childNodeAnchor - IF(h.childnodeanchor = :originalNodeAnchor, :newNodeAnchor, h.childnodeanchor) as childnodeanchor, - h.position, - h.subtreetags, - h.dimensionspacepointhash, - :targetContentStreamLayer as contentstreamlayer - FROM - {$this->tableNames->hierarchyRelation()} h - WHERE - :originalNodeAnchor IN (h.childnodeanchor, h.parentnodeanchor) - AND h.contentstreamlayer IN (:contentStreamLayers) - SQL; - - try { - $this->dbal->executeStatement($copyHierarchyRelationStatement, [ - 'newNodeAnchor' => $copiedNode->relationAnchorPoint->value, - 'originalNodeAnchor' => $anchorPoint->value, - 'contentStreamLayers' => $contentStreamLayersWhereWriteOccurs->toIntArray(), - 'targetContentStreamLayer' => $contentStreamLayersWhereWriteOccurs->getWriteLayer()->value, - ], [ - 'contentStreamLayers' => ArrayParameterType::INTEGER, - ]); - } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to update hierarchy relation: %s', $e->getMessage()), 1716486444, $e); - } + try { + $this->dbal->executeStatement($copyHierarchyRelationStatement, [ + 'newNodeAnchor' => $copiedNode->relationAnchorPoint->value, + 'originalNodeAnchor' => $anchorPoint->value, + 'contentStreamLayers' => $contentStreamLayersWhereWriteOccurs->toIntArray(), + 'targetContentStreamLayer' => $contentStreamLayersWhereWriteOccurs->getWriteLayer()->value, + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER, + ]); + } catch (DBALException $e) { + throw new \RuntimeException(sprintf('Failed to update hierarchy relation: %s', $e->getMessage()), 1716486444, $e); } - - // reference relation rows need to be copied as well! $this->copyReferenceRelations( $anchorPoint, From 9339512d9802c882a046d4e7b84b382eecf69f24 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:41:24 +0200 Subject: [PATCH 16/17] PATCH: Fix findNodeAggregatesTaggedBy --- .../src/Domain/Repository/ContentGraph.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 75f1e5a9749..cb502aa3412 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -341,7 +341,7 @@ public function findNodeAggregatesTaggedBy(SubtreeTag $subtreeTag): NodeAggregat $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.contentstreamlayer, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') // select the subtree tags from tagged (t) h and then join h again to fetch all node rows in that aggregate - ->from($this->hierarchyRelationStatement->where('JSON_EXTRACT(th.subtreetags, :tagPath) LIKE "true"')->toSql(), 'th') + ->from($this->hierarchyRelationStatement->where('JSON_EXTRACT(h.subtreetags, :tagPath) LIKE "true"')->toSql(), 'th') ->innerJoin('th', $this->hierarchyRelationStatement->toSql(), 'h', 'th.childnodeanchor = h.childnodeanchor') ->innerJoin('h', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') From 8ca5757ed1c35764b9d965d98e30b8358b238169 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:04:31 +0200 Subject: [PATCH 17/17] WIP: Migrate `ProjectionIntegrityViolationDetector` to content stream layers --- .../ProjectionIntegrityViolationDetector.php | 193 ++++++++++-------- 1 file changed, 104 insertions(+), 89 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php index 968ef36c437..4de238945d8 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php @@ -14,9 +14,11 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception as DBALException; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; +use Neos\ContentGraph\DoctrineDbalAdapter\HierarchyRelationStatement; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\Projection\ContentGraph\ProjectionIntegrityViolationDetectorInterface; @@ -32,10 +34,13 @@ */ final class ProjectionIntegrityViolationDetector implements ProjectionIntegrityViolationDetectorInterface { + private readonly HierarchyRelationStatement $hierarchyRelationStatement; + public function __construct( private readonly Connection $dbal, private readonly ContentGraphTableNames $tableNames, ) { + $this->hierarchyRelationStatement = HierarchyRelationStatement::for($this->tableNames); } public function hierarchyIntegrityIsProvided(): Result @@ -89,18 +94,23 @@ public function hierarchyIntegrityIsProvided(): Result )); } + // FIXME the violation does not consider multiple layers $hierarchyRelationsAppearingMultipleTimesStatement = <<tableNames->hierarchyRelation()} h + h.dimensionspacepointhash, + h.contentstreamlayer + FROM {$this->tableNames->hierarchyRelation()} AS h LEFT JOIN {$this->tableNames->node()} p ON h.parentnodeanchor = p.relationanchorpoint LEFT JOIN {$this->tableNames->node()} c ON h.childnodeanchor = c.relationanchorpoint WHERE h.parentnodeanchor != :rootNodeAnchor GROUP BY - p.nodeaggregateid, c.nodeaggregateid, - h.dimensionspacepointhash, h.contentstreamdbid + p.nodeaggregateid, + c.nodeaggregateid, + h.dimensionspacepointhash, + h.contentstreamlayer HAVING uniquenessCounter > 1 SQL; try { @@ -125,16 +135,19 @@ public function siblingsAreDistinctlySorted(): Result { $result = new Result(); + // FIXME the violation does not consider multiple layers $ambiguouslySortedHierarchyRelationStatement = <<tableNames->hierarchyRelation()} GROUP BY + contentstreamlayer, position, parentnodeanchor, - contentstreamdbid, dimensionspacepointhash HAVING COUNT(position) > 1 @@ -168,7 +181,7 @@ public function siblingsAreDistinctlySorted(): Result $result->addError(new Error( 'Siblings ' . implode(', ', array_map(static fn (array $record) => $record['nodeaggregateid'], $ambiguouslySortedNodeRecords)) - . ' are ambiguously sorted in content stream ' . $hierarchyRelationRecord['contentstreamdbid'] + . ' are ambiguously sorted in content stream ' . $hierarchyRelationRecord['contentstreamlayer'] . ' and dimension space point ' . $dimensionSpacePoints[$hierarchyRelationRecord['dimensionspacepointhash']]?->toJson(), self::ERROR_CODE_SIBLINGS_ARE_AMBIGUOUSLY_SORTED )); @@ -182,7 +195,7 @@ public function tetheredNodesAreNamed(): Result $result = new Result(); $unnamedTetheredNodesStatement = <<tableNames->node()} n INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint @@ -190,7 +203,7 @@ public function tetheredNodesAreNamed(): Result n.classification = :tethered AND n.name IS NULL GROUP BY - n.nodeaggregateid, h.contentstreamdbid + n.nodeaggregateid, h.contentstreamlayer SQL; try { $unnamedTetheredNodeRecords = $this->dbal->fetchAllAssociative($unnamedTetheredNodesStatement, [ @@ -203,7 +216,7 @@ public function tetheredNodesAreNamed(): Result foreach ($unnamedTetheredNodeRecords as $unnamedTetheredNodeRecord) { $result->addError(new Error( 'Node aggregate ' . $unnamedTetheredNodeRecord['nodeaggregateid'] - . ' is unnamed in content stream ' . $unnamedTetheredNodeRecord['contentstreamdbid'] . '.', + . ' is unnamed in content stream ' . $unnamedTetheredNodeRecord['contentstreamlayer'] . '.', self::ERROR_CODE_TETHERED_NODE_IS_UNNAMED )); } @@ -225,7 +238,7 @@ public function subtreeTagsAreInherited(): Result {$this->tableNames->hierarchyRelation()} h INNER JOIN {$this->tableNames->hierarchyRelation()} ph ON ph.childnodeanchor = h.parentnodeanchor - AND ph.contentstreamdbid = h.contentstreamdbid + AND ph.contentstreamlayer = h.contentstreamlayer AND ph.dimensionspacepointhash = h.dimensionspacepointhash WHERE EXISTS ( @@ -279,7 +292,7 @@ public function referenceIntegrityIsProvided(): Result $referenceRelationRecordsWithInvalidTargetStatement = <<tableNames->node()} d INNER JOIN {$this->tableNames->hierarchyRelation()} dh ON d.relationanchorpoint = dh.childnodeanchor ) ON r.destinationnodeaggregateid = d.nodeaggregateid - AND sh.contentstreamdbid = dh.contentstreamdbid + AND sh.contentstreamlayer = dh.contentstreamlayer AND sh.dimensionspacepointhash = dh.dimensionspacepointhash WHERE d.nodeaggregateid IS NULL GROUP BY s.nodeaggregateid, - sh.contentstreamdbid, + sh.contentstreamlayer, r.destinationnodeaggregateid SQL; try { @@ -309,7 +322,7 @@ public function referenceIntegrityIsProvided(): Result $result->addError(new Error( 'Destination node aggregate ' . $record['destinationNodeAggregateId'] . ' does not cover any dimension space points the source ' . $record['sourceNodeAggregateId'] - . ' does in content stream ' . $record['contentstreamdbid'], + . ' does in content stream ' . $record['contentstreamlayer'], self::ERROR_CODE_REFERENCE_INTEGRITY_IS_COMPROMISED )); } @@ -336,11 +349,9 @@ public function allNodesAreConnectedToARootNodePerSubgraph(): Result SELECT h.childnodeanchor FROM - {$this->tableNames->hierarchyRelation()} h + {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h WHERE h.parentnodeanchor = :rootAnchorPoint - AND h.contentstreamdbid = :contentStreamDbId - AND h.dimensionspacepointhash = :dimensionSpacePointHash UNION -- -------------------------------- -- RECURSIVE query: do one "child" query step @@ -349,28 +360,25 @@ public function allNodesAreConnectedToARootNodePerSubgraph(): Result h.childnodeanchor FROM subgraph p - INNER JOIN {$this->tableNames->hierarchyRelation()} h - on h.parentnodeanchor = p.childnodeanchor - WHERE - h.contentstreamdbid = :contentStreamDbId - AND h.dimensionspacepointhash = :dimensionSpacePointHash + INNER JOIN {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h + ON h.parentnodeanchor = p.childnodeanchor ) SELECT nodeaggregateid FROM {$this->tableNames->node()} n - INNER JOIN {$this->tableNames->hierarchyRelation()} h + INNER JOIN {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h ON h.childnodeanchor = n.relationanchorpoint WHERE - h.contentstreamdbid = :contentStreamDbId - AND h.dimensionspacepointhash = :dimensionSpacePointHash - AND relationanchorpoint NOT IN (SELECT * FROM subgraph) + relationanchorpoint NOT IN (SELECT * FROM subgraph) SQL; - foreach ($this->findProjectedContentStreamDbIds() as $contentStreamDbId) { + foreach ($this->findProjectedContentStreamLayers() as $contentStreamLayers) { foreach ($this->findProjectedDimensionSpacePoints() as $dimensionSpacePoint) { try { $nodeAggregateIdsInCycles = $this->dbal->fetchFirstColumn($nodeAggregateIdsInCyclesStatement, [ 'rootAnchorPoint' => NodeRelationAnchorPoint::forRootEdge()->value, - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load cyclic node relations: %s', $e->getMessage()), 1716493090, $e); @@ -378,7 +386,7 @@ public function allNodesAreConnectedToARootNodePerSubgraph(): Result if (!empty($nodeAggregateIdsInCycles)) { $result->addError(new Error( - 'Subgraph defined by content stream ' . $contentStreamDbId->value + 'Subgraph defined by content stream ' . $contentStreamLayers->toDebugString() . ' and dimension space point ' . $dimensionSpacePoint->toJson() . ' is cyclic for node aggregates ' . implode(',', $nodeAggregateIdsInCycles), @@ -410,22 +418,21 @@ public function nodeAggregateIdsAreUniquePerSubgraph(): Result n.nodeaggregateid, COUNT(n.relationanchorpoint) FROM {$this->tableNames->node()} n - INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint - WHERE - h.contentstreamdbid = :contentStreamDbId - AND h.dimensionspacepointhash = :dimensionSpacePointHash + INNER JOIN {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h ON h.childnodeanchor = n.relationanchorpoint GROUP BY n.nodeaggregateid HAVING COUNT(DISTINCT(n.relationanchorpoint)) > 1 SQL; - foreach ($this->findProjectedContentStreamDbIds() as $contentStreamDbId) { + foreach ($this->findProjectedContentStreamLayers() as $contentStreamLayers) { foreach ($this->findProjectedDimensionSpacePoints() as $dimensionSpacePoint) { try { $ambiguousNodeAggregateRecords = $this->dbal->fetchAllAssociative($ambiguousNodeAggregatesStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load ambiguous node aggregates: %s', $e->getMessage()), 1716494110, $e); @@ -433,7 +440,7 @@ public function nodeAggregateIdsAreUniquePerSubgraph(): Result foreach ($ambiguousNodeAggregateRecords as $ambiguousRecord) { $result->addError(new Error( 'Node aggregate ' . $ambiguousRecord['nodeaggregateid'] - . ' is ambiguous in content stream ' . $contentStreamDbId->value + . ' is ambiguous in content stream ' . $contentStreamLayers->toDebugString() . ' and dimension space point ' . $dimensionSpacePoint->toJson(), self::ERROR_CODE_AMBIGUOUS_NODE_AGGREGATE_IN_SUBGRAPH )); @@ -452,22 +459,21 @@ public function allNodesHaveAtMostOneParentPerSubgraph(): Result c.nodeaggregateid FROM {$this->tableNames->node()} c - INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = c.relationanchorpoint - WHERE - h.contentstreamdbid = :contentStreamDbId - AND h.dimensionspacepointhash = :dimensionSpacePointHash + INNER JOIN {$this->hierarchyRelationStatement->where('h.dimensionspacepointhash = :dimensionSpacePointHash')->toSql()} h ON h.childnodeanchor = c.relationanchorpoint GROUP BY c.relationanchorpoint HAVING COUNT(DISTINCT(h.parentnodeanchor)) > 1 SQL; - foreach ($this->findProjectedContentStreamDbIds() as $contentStreamDbId) { + foreach ($this->findProjectedContentStreamLayers() as $contentStreamLayers) { foreach ($this->findProjectedDimensionSpacePoints() as $dimensionSpacePoint) { try { $nodeRecordsWithMultipleParents = $this->dbal->fetchAllAssociative($nodeRecordsWithMultipleParentsStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'dimensionSpacePointHash' => $dimensionSpacePoint->hash + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load nodes with multiple parents: %s', $e->getMessage()), 1716494223, $e); @@ -476,7 +482,7 @@ public function allNodesHaveAtMostOneParentPerSubgraph(): Result foreach ($nodeRecordsWithMultipleParents as $record) { $result->addError(new Error( 'Node aggregate ' . $record['nodeaggregateid'] - . ' has multiple parents in content stream ' . $contentStreamDbId->value + . ' has multiple parents in content stream ' . $contentStreamLayers->toDebugString() . ' and dimension space point ' . $dimensionSpacePoint->toJson(), self::ERROR_CODE_NODE_HAS_MULTIPLE_PARENTS )); @@ -495,21 +501,22 @@ public function nodeAggregatesAreConsistentlyTypedPerContentStream(): Result DISTINCT n.nodetypename FROM {$this->tableNames->node()} n - INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN {$this->hierarchyRelationStatement->toSql()} h ON h.childnodeanchor = n.relationanchorpoint WHERE - h.contentstreamdbid = :contentStreamDbId - AND n.nodeaggregateid = :nodeAggregateId + n.nodeaggregateid = :nodeAggregateId SQL; - foreach ($this->findProjectedContentStreamDbIds() as $contentStreamDbId) { + foreach ($this->findProjectedContentStreamLayers() as $contentStreamLayers) { foreach ( $this->findProjectedNodeAggregateIdsInContentStream( - $contentStreamDbId + $contentStreamLayers ) as $nodeAggregateId ) { try { $nodeTypeNames = $this->dbal->fetchFirstColumn($nodeAggregatesStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'nodeAggregateId' => $nodeAggregateId->value + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load node type names: %s', $e->getMessage()), 1716494446, $e); @@ -518,7 +525,7 @@ public function nodeAggregatesAreConsistentlyTypedPerContentStream(): Result if (count($nodeTypeNames) > 1) { $result->addError(new Error( 'Node aggregate ' . $nodeAggregateId->value - . ' in content stream ' . $contentStreamDbId->value + . ' in content stream ' . $contentStreamLayers->toDebugString() . ' is of ambiguous type ("' . implode('","', $nodeTypeNames) . '")', self::ERROR_CODE_NODE_AGGREGATE_IS_AMBIGUOUSLY_TYPED )); @@ -537,21 +544,23 @@ public function nodeAggregatesAreConsistentlyClassifiedPerContentStream(): Resul DISTINCT n.classification FROM {$this->tableNames->node()} n - INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN {$this->hierarchyRelationStatement->toSql()} h ON h.childnodeanchor = n.relationanchorpoint WHERE - h.contentstreamdbid = :contentStreamDbId - AND n.nodeaggregateid = :nodeAggregateId + n.nodeaggregateid = :nodeAggregateId SQL; - foreach ($this->findProjectedContentStreamDbIds() as $contentStreamDbId) { + + foreach ($this->findProjectedContentStreamLayers() as $contentStreamLayers) { foreach ( $this->findProjectedNodeAggregateIdsInContentStream( - $contentStreamDbId + $contentStreamLayers ) as $nodeAggregateId ) { try { $classifications = $this->dbal->fetchFirstColumn($nodeAggregatesStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'nodeAggregateId' => $nodeAggregateId->value + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load node classifications: %s', $e->getMessage()), 1716494466, $e); @@ -560,7 +569,7 @@ public function nodeAggregatesAreConsistentlyClassifiedPerContentStream(): Resul if (count($classifications) > 1) { $result->addError(new Error( 'Node aggregate ' . $nodeAggregateId->value - . ' in content stream ' . $contentStreamDbId->value + . ' in content stream ' . $contentStreamLayers->toDebugString() . ' is ambiguously classified ("' . implode('","', $classifications) . '")', self::ERROR_CODE_NODE_AGGREGATE_IS_AMBIGUOUSLY_CLASSIFIED )); @@ -578,19 +587,19 @@ public function childNodeCoverageIsASubsetOfParentNodeCoverage(): Result SELECT n.nodeaggregateid, c.dimensionspacepointhash FROM - {$this->tableNames->hierarchyRelation()} c + {$this->hierarchyRelationStatement->toSql()} c INNER JOIN {$this->tableNames->node()} n ON c.childnodeanchor = n.relationanchorpoint - LEFT JOIN {$this->tableNames->hierarchyRelation()} p ON c.parentnodeanchor = p.childnodeanchor + LEFT JOIN {$this->hierarchyRelationStatement->toSql()} p ON c.parentnodeanchor = p.childnodeanchor WHERE - c.contentstreamdbid = :contentStreamDbId - AND p.contentstreamdbid = :contentStreamDbId - AND c.dimensionspacepointhash = p.dimensionspacepointhash + c.dimensionspacepointhash = p.dimensionspacepointhash AND p.childnodeanchor IS NULL SQL; - foreach ($this->findProjectedContentStreamDbIds() as $contentStreamDbId) { + foreach ($this->findProjectedContentStreamLayers() as $contentStreamLayers) { try { $excessivelyCoveringNodeRecords = $this->dbal->fetchAllAssociative($excessivelyCoveringStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value + 'contentStreamLayers' => $contentStreamLayers->toIntArray() + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load excessively covering nodes: %s', $e->getMessage()), 1716494618, $e); @@ -598,7 +607,7 @@ public function childNodeCoverageIsASubsetOfParentNodeCoverage(): Result foreach ($excessivelyCoveringNodeRecords as $excessivelyCoveringNodeRecord) { $result->addError(new Error( 'Node aggregate ' . $excessivelyCoveringNodeRecord['nodeaggregateid'] - . ' in content stream ' . $contentStreamDbId->value + . ' in content stream ' . $contentStreamLayers->toDebugString() . ' covers dimension space point hash ' . $excessivelyCoveringNodeRecord['dimensionspacepointhash'] . ' but its parent does not.', self::ERROR_CODE_CHILD_NODE_COVERAGE_IS_NO_SUBSET_OF_PARENT_NODE_COVERAGE @@ -617,28 +626,27 @@ public function allNodesCoverTheirOrigin(): Result nodeaggregateid, origindimensionspacepointhash FROM {$this->tableNames->node()} n - INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN {$this->hierarchyRelationStatement->toSql()} h ON h.childnodeanchor = n.relationanchorpoint WHERE - h.contentstreamdbid = :contentStreamDbId - AND nodeaggregateid NOT IN ( + nodeaggregateid NOT IN ( -- this query finds all nodes whose origin *IS COVERED* by an incoming hierarchy relation. SELECT n.nodeaggregateid FROM {$this->tableNames->node()} n - LEFT JOIN {$this->tableNames->hierarchyRelation()} p ON + LEFT JOIN {$this->hierarchyRelationStatement->toSql()} p ON p.childnodeanchor = n.relationanchorpoint AND p.dimensionspacepointhash = n.origindimensionspacepointhash - WHERE - p.contentstreamdbid = :contentStreamDbId - ) - AND classification != :rootClassification + ) + AND classification != :rootClassification SQL; - foreach ($this->findProjectedContentStreamDbIds() as $contentStreamDbId) { + foreach ($this->findProjectedContentStreamLayers() as $contentStreamLayers) { try { $nodeRecordsWithMissingOriginCoverage = $this->dbal->fetchAllAssociative($nodesWithMissingOriginCoverageStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), 'rootClassification' => NodeAggregateClassification::CLASSIFICATION_ROOT->value + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load nodes with missing origin coverage: %s', $e->getMessage()), 1716494752, $e); @@ -647,7 +655,7 @@ public function allNodesCoverTheirOrigin(): Result foreach ($nodeRecordsWithMissingOriginCoverage as $nodeRecord) { $result->addError(new Error( 'Node aggregate ' . $nodeRecord['nodeaggregateid'] - . ' in content stream ' . $contentStreamDbId->value + . ' in content stream ' . $contentStreamLayers->toDebugString() . ' does not cover its origin dimension space point hash ' . $nodeRecord['origindimensionspacepointhash'] . '.', self::ERROR_CODE_NODE_DOES_NOT_COVER_ITS_ORIGIN @@ -659,21 +667,28 @@ public function allNodesCoverTheirOrigin(): Result } /** - * Returns all content stream ids - * - * @return iterable + * @return iterable */ - private function findProjectedContentStreamDbIds(): iterable + private function findProjectedContentStreamLayers(): iterable { - $contentStreamDbIdsStatement = <<tableNames->hierarchyRelation()} + $contentStreamLayersStatement = <<tableNames->contentStreamLayer()} SQL; try { - $contentStreamDbIds = $this->dbal->fetchFirstColumn($contentStreamDbIdsStatement); + $contentStreamLayers = $this->dbal->fetchAllAssociative($contentStreamLayersStatement); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load content stream ids: %s', $e->getMessage()), 1716494814, $e); } - return array_map(fn (string $value) => ContentStreamDbId::fromInt((int)$value), $contentStreamDbIds); + + foreach (array_unique(array_column($contentStreamLayers, 'contentstreamid')) as $contentStreamId) { + $items = []; + foreach ($contentStreamLayers as $row) { + if ($row['contentstreamid'] === $contentStreamId) { + $items[] = $row['contentstreamlayer']; + } + } + yield ContentStreamLayers::fromArray($items); + } } /** @@ -696,20 +711,20 @@ private function findProjectedDimensionSpacePoints(): DimensionSpacePointSet * @return array */ protected function findProjectedNodeAggregateIdsInContentStream( - ContentStreamDbId $contentStreamDbId + ContentStreamLayers $contentStreamLayers ): array { $nodeAggregateIdsStatement = <<tableNames->node()} n - INNER JOIN {$this->tableNames->hierarchyRelation()} h ON h.childnodeanchor = n.relationanchorpoint - WHERE - h.contentstreamdbid = :contentStreamDbId + INNER JOIN {$this->hierarchyRelationStatement->toSql()} h ON h.childnodeanchor = n.relationanchorpoint SQL; try { $nodeAggregateIds = $this->dbal->fetchFirstColumn($nodeAggregateIdsStatement, [ - 'contentStreamDbId' => $contentStreamDbId->value, + 'contentStreamLayers' => $contentStreamLayers->toIntArray(), + ], [ + 'contentStreamLayers' => ArrayParameterType::INTEGER ]); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to load node aggregate ids for content stream: %s', $e->getMessage()), 1716495988, $e);