diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index b403ce81fa4..5daf9aec36e 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -16,6 +16,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraph; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePoints; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; @@ -83,6 +84,8 @@ final class DoctrineDbalContentGraphProjection implements ProjectionInterface, W private DbalCheckpointStorage $checkpointStorage; + private DimensionSpacePoints $dimensionSpacePoints; + public function __construct( private readonly DbalClientInterface $dbalClient, private readonly NodeFactory $nodeFactory, @@ -96,6 +99,8 @@ public function __construct( $this->tableNamePrefix . '_checkpoint', self::class ); + + $this->dimensionSpacePoints = new DimensionSpacePoints($this->dbalClient->getConnection(), $this->tableNamePrefix); } protected function getProjectionContentGraph(): ProjectionContentGraph @@ -175,6 +180,7 @@ private function truncateDatabaseTables(): void $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_hierarchyrelation'); $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_referencerelation'); $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_restrictionrelation'); + $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_dimensionspacepoints'); } public function canHandle(EventInterface $event): bool @@ -644,7 +650,6 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void childnodeanchor, `name`, position, - dimensionspacepoint, dimensionspacepointhash, contentstreamid ) @@ -653,7 +658,6 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void h.childnodeanchor, h.name, h.position, - h.dimensionspacepoint, h.dimensionspacepointhash, "' . $event->newContentStreamId->value . '" AS contentstreamid FROM @@ -1111,6 +1115,7 @@ private function copyReferenceRelations( private function whenDimensionSpacePointWasMoved(DimensionSpacePointWasMoved $event): void { + $this->dimensionSpacePoints->insertDimensionSpacePoint($event->target); $this->transactional(function () use ($event) { // the ordering is important - we first update the OriginDimensionSpacePoints, as we need the // hierarchy relations for this query. Then, we update the Hierarchy Relations. @@ -1150,7 +1155,6 @@ function (NodeRecord $nodeRecord) use ($event) { ' UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h SET - h.dimensionspacepoint = :newDimensionSpacePoint, h.dimensionspacepointhash = :newDimensionSpacePointHash WHERE h.dimensionspacepointhash = :originalDimensionSpacePointHash @@ -1159,7 +1163,6 @@ function (NodeRecord $nodeRecord) use ($event) { [ 'originalDimensionSpacePointHash' => $event->source->hash, 'newDimensionSpacePointHash' => $event->target->hash, - 'newDimensionSpacePoint' => $event->target->toJson(), 'contentStreamId' => $event->contentStreamId->value ] ); @@ -1185,6 +1188,7 @@ function (NodeRecord $nodeRecord) use ($event) { private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded $event): void { + $this->dimensionSpacePoints->insertDimensionSpacePoint($event->target); $this->transactional(function () use ($event) { // 1) hierarchy relations $this->getDatabaseConnection()->executeStatement( @@ -1194,7 +1198,6 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded childnodeanchor, `name`, position, - dimensionspacepoint, dimensionspacepointhash, contentstreamid ) @@ -1203,7 +1206,6 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded h.childnodeanchor, h.name, h.position, - :newDimensionSpacePoint AS dimensionspacepoint, :newDimensionSpacePointHash AS dimensionspacepointhash, h.contentstreamid FROM @@ -1214,7 +1216,6 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded 'contentStreamId' => $event->contentStreamId->value, 'sourceDimensionSpacePointHash' => $event->source->hash, 'newDimensionSpacePointHash' => $event->target->hash, - 'newDimensionSpacePoint' => $event->target->toJson(), ] ); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php index d3022e2c290..1639f86cba1 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php @@ -4,6 +4,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePoints; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; use Neos\ContentRepository\Core\Factory\ContentRepositoryId; @@ -40,13 +41,16 @@ public function build( $projectionFactoryDependencies->contentRepositoryId ); + + return new ContentGraphProjection( new DoctrineDbalContentGraphProjection( $this->dbalClient, new NodeFactory( $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, - $projectionFactoryDependencies->propertyConverter + $projectionFactoryDependencies->propertyConverter, + new DimensionSpacePoints($this->dbalClient->getConnection(), $tableNamePrefix) ), $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index b42e1d2a321..1240f11a109 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -4,7 +4,6 @@ use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Column; -use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Types\Type; @@ -29,7 +28,8 @@ public function buildSchema(AbstractSchemaManager $schemaManager): Schema $this->createNodeTable(), $this->createHierarchyRelationTable(), $this->createReferenceRelationTable(), - $this->createRestrictionRelationTable() + $this->createRestrictionRelationTable(), + $this->createDimensionSpacePointsTable() ]); } @@ -38,7 +38,6 @@ private function createNodeTable(): Table $table = new Table($this->tableNamePrefix . '_node', [ DbalSchemaFactory::columnForNodeAnchorPoint('relationanchorpoint')->setAutoincrement(true), DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotnull(false), - DbalSchemaFactory::columnForDimensionSpacePoint('origindimensionspacepoint')->setNotnull(false), DbalSchemaFactory::columnForDimensionSpacePointHash('origindimensionspacepointhash')->setNotnull(false), DbalSchemaFactory::columnForNodeTypeName('nodetypename'), (new Column('properties', Type::getType(Types::TEXT)))->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), @@ -61,7 +60,6 @@ private function createHierarchyRelationTable(): Table (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), (new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotnull(true), - DbalSchemaFactory::columnForDimensionSpacePoint('dimensionspacepoint')->setNotnull(true), DbalSchemaFactory::columnForDimensionSpacePointHash('dimensionspacepointhash')->setNotnull(true), DbalSchemaFactory::columnForNodeAnchorPoint('parentnodeanchor'), DbalSchemaFactory::columnForNodeAnchorPoint('childnodeanchor') @@ -75,6 +73,17 @@ private function createHierarchyRelationTable(): Table ->addIndex(['contentstreamid', 'dimensionspacepointhash']); } + private function createDimensionSpacePointsTable(): Table + { + $table = new Table($this->tableNamePrefix . '_dimensionspacepoints', [ + DbalSchemaFactory::columnForDimensionSpacePointHash('hash')->setNotnull(true), + DbalSchemaFactory::columnForDimensionSpacePoint('dimensionspacepoint')->setNotnull(true) + ]); + + return $table + ->setPrimaryKey(['hash']); + } + private function createReferenceRelationTable(): Table { $table = new Table($this->tableNamePrefix . '_referencerelation', [ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php index 3908f24fcaa..b731a052600 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php @@ -15,6 +15,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection; use Doctrine\DBAL\Connection; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePoints; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -42,12 +43,14 @@ public function __construct( */ public function addToDatabase(Connection $databaseConnection, string $tableNamePrefix): void { + $dimensionSpacePoints = new DimensionSpacePoints($databaseConnection, $tableNamePrefix); + $dimensionSpacePoints->insertDimensionSpacePoint($this->dimensionSpacePoint); + $databaseConnection->insert($tableNamePrefix . '_hierarchyrelation', [ 'parentnodeanchor' => $this->parentNodeAnchor->value, 'childnodeanchor' => $this->childNodeAnchor->value, 'name' => $this->name?->value, 'contentstreamid' => $this->contentStreamId->value, - 'dimensionspacepoint' => $this->dimensionSpacePoint->toJson(), 'dimensionspacepointhash' => $this->dimensionSpacePointHash, 'position' => $this->position ]); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php index d34282a4c91..f5639c1f88c 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php @@ -15,7 +15,9 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Types\Types; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePoints; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; use Neos\ContentRepository\Core\Projection\ContentGraph\Timestamps; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; @@ -55,7 +57,6 @@ public function updateToDatabase(Connection $databaseConnection, string $tableNa $tableNamePrefix . '_node', [ 'nodeaggregateid' => $this->nodeAggregateId->value, - 'origindimensionspacepoint' => json_encode($this->originDimensionSpacePoint), 'origindimensionspacepointhash' => $this->originDimensionSpacePointHash, 'properties' => json_encode($this->properties), 'nodetypename' => $this->nodeTypeName->value, @@ -138,9 +139,11 @@ public static function createNewInDatabase( ?NodeName $nodeName, Timestamps $timestamps, ): self { + $dimensionSpacePoints = new DimensionSpacePoints($databaseConnection, $tableNamePrefix); + $dimensionSpacePoints->insertDimensionSpacePointByHashAndCoordinates($originDimensionSpacePointHash, $originDimensionSpacePoint); + $databaseConnection->insert($tableNamePrefix . '_node', [ 'nodeaggregateid' => $nodeAggregateId->value, - 'origindimensionspacepoint' => json_encode($originDimensionSpacePoint), 'origindimensionspacepointhash' => $originDimensionSpacePointHash, 'properties' => json_encode($properties), 'nodetypename' => $nodeTypeName->value, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php index 1ee0e3e106b..974af6fed0b 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php @@ -67,8 +67,8 @@ public function hierarchyIntegrityIsProvided(): Result } $invalidlyHashedHierarchyRelationRecords = $this->client->getConnection()->executeQuery( - 'SELECT * FROM ' . $this->tableNamePrefix . '_hierarchyrelation - WHERE dimensionspacepointhash != MD5(dimensionspacepoint)' + 'SELECT * FROM ' . $this->tableNamePrefix . '_hierarchyrelation h LEFT JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON dsp.hash = h.dimensionspacepointhash + HAVING dsp.dimensionspacepoint IS NULL' )->fetchAllAssociative(); foreach ($invalidlyHashedHierarchyRelationRecords as $record) { @@ -179,7 +179,7 @@ public function restrictionsArePropagatedRecursively(): Result { $result = new Result(); $nodeRecordsWithMissingRestrictions = $this->client->getConnection()->executeQuery( - 'SELECT c.nodeaggregateid, h.contentstreamid, h.dimensionspacepoint + 'SELECT c.nodeaggregateid, h.contentstreamid, h.dimensionspacepointhash FROM ' . $this->tableNamePrefix . '_hierarchyrelation h INNER JOIN ' . $this->tableNamePrefix . '_node p ON p.relationanchorpoint = h.parentnodeanchor @@ -200,7 +200,7 @@ public function restrictionsArePropagatedRecursively(): Result $result->addError(new Error( 'Node aggregate ' . $nodeRecord['nodeaggregateid'] . ' misses a restriction relation in content stream ' . $nodeRecord['contentstreamid'] - . ' and dimension space point ' . $nodeRecord['dimensionspacepoint'], + . ' and dimension space point hash ' . $nodeRecord['dimensionspacepointhash'], self::ERROR_CODE_NODE_HAS_MISSING_RESTRICTION )); } @@ -557,7 +557,7 @@ public function childNodeCoverageIsASubsetOfParentNodeCoverage(): Result $result = new Result(); foreach ($this->findProjectedContentStreamIds() as $contentStreamId) { $excessivelyCoveringNodeRecords = $this->client->getConnection()->executeQuery( - 'SELECT n.nodeaggregateid, c.dimensionspacepoint + 'SELECT n.nodeaggregateid, c.dimensionspacepointhash FROM ' . $this->tableNamePrefix . '_hierarchyrelation c INNER JOIN ' . $this->tableNamePrefix . '_node n ON c.childnodeanchor = n.relationanchorpoint @@ -576,7 +576,7 @@ public function childNodeCoverageIsASubsetOfParentNodeCoverage(): Result $result->addError(new Error( 'Node aggregate ' . $excessivelyCoveringNodeRecord['nodeaggregateid'] . ' in content stream ' . $contentStreamId->value - . ' covers dimension space point ' . $excessivelyCoveringNodeRecord['dimensionspacepoint'] + . ' 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 )); @@ -594,7 +594,7 @@ public function allNodesCoverTheirOrigin(): Result $result = new Result(); foreach ($this->findProjectedContentStreamIds() as $contentStreamId) { $nodeRecordsWithMissingOriginCoverage = $this->client->getConnection()->executeQuery( - 'SELECT nodeaggregateid, origindimensionspacepoint + 'SELECT nodeaggregateid, origindimensionspacepointhash FROM ' . $this->tableNamePrefix . '_node n INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint @@ -620,7 +620,7 @@ public function allNodesCoverTheirOrigin(): Result $result->addError(new Error( 'Node aggregate ' . $nodeRecord['nodeaggregateid'] . ' in content stream ' . $contentStreamId->value - . ' does not cover its origin dimension space point ' . $nodeRecord['origindimensionspacepoint'] + . ' does not cover its origin dimension space point hash ' . $nodeRecord['origindimensionspacepointhash'] . '.', self::ERROR_CODE_NODE_DOES_NOT_COVER_ITS_ORIGIN )); @@ -656,7 +656,7 @@ protected function findProjectedContentStreamIds(): iterable protected function findProjectedDimensionSpacePoints(): DimensionSpacePointSet { $records = $this->client->getConnection()->executeQuery( - 'SELECT DISTINCT dimensionspacepoint FROM ' . $this->tableNamePrefix . '_hierarchyrelation' + 'SELECT dimensionspacepoint FROM ' . $this->tableNamePrefix . '_dimensionspacepoints' )->fetchAllAssociative(); $records = array_map(function (array $record) { diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 3ede770bd5d..9e1ae0cfe42 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -137,9 +137,10 @@ public function findRootNodeAggregates( FindRootNodeAggregatesFilter $filter, ): NodeAggregates { $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.contentstreamid, h.name, h.dimensionspacepoint AS covereddimensionspacepoint') + ->select('n.*, h.contentstreamid, h.name, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNamePrefix . '_node', 'n') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') ->setParameters([ @@ -160,9 +161,10 @@ public function findNodeAggregatesByType( NodeTypeName $nodeTypeName ): iterable { $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.contentstreamid, h.name, h.dimensionspacepoint AS covereddimensionspacepoint') + ->select('n.*, h.contentstreamid, h.name, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNamePrefix . '_node', 'n') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('h.contentstreamid = :contentStreamId') ->andWhere('n.nodetypename = :nodeTypeName') ->setParameters([ @@ -177,9 +179,10 @@ public function findNodeAggregateById( NodeAggregateId $nodeAggregateId ): ?NodeAggregate { $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.contentstreamid, h.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') + ->select('n.*, h.name, h.contentstreamid, dsp.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') ->from($this->tableNamePrefix . '_hierarchyrelation', 'h') ->innerJoin('h', $this->tableNamePrefix . '_node', 'n', 'n.relationanchorpoint = h.childnodeanchor') + ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->leftJoin('h', $this->tableNamePrefix . '_restrictionrelation', 'r', 'r.originnodeaggregateid = n.nodeaggregateid AND r.contentstreamid = :contentStreamId AND r.affectednodeaggregateid = n.nodeaggregateid AND r.dimensionspacepointhash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = :nodeAggregateId') ->andWhere('h.contentstreamid = :contentStreamId') @@ -202,11 +205,12 @@ public function findParentNodeAggregates( NodeAggregateId $childNodeAggregateId ): iterable { $queryBuilder = $this->createQueryBuilder() - ->select('pn.*, ph.name, ph.contentstreamid, ph.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') + ->select('pn.*, ph.name, ph.contentstreamid, pdsp.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') ->from($this->tableNamePrefix . '_node', 'pn') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->innerJoin('ph', $this->tableNamePrefix . '_dimensionspacepoints', 'pdsp', 'pdsp.hash = ph.dimensionspacepointhash') ->leftJoin('ph', $this->tableNamePrefix . '_restrictionrelation', 'r', 'r.originnodeaggregateid = pn.nodeaggregateid AND r.contentstreamid = :contentStreamId AND r.affectednodeaggregateid = pn.nodeaggregateid AND r.dimensionspacepointhash = ph.dimensionspacepointhash') ->where('cn.nodeaggregateid = :nodeAggregateId') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -235,9 +239,10 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( ->andWhere('cn.origindimensionspacepointhash = :childOriginDimensionSpacePointHash'); $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.contentstreamid, h.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') + ->select('n.*, h.name, h.contentstreamid, dsp.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') ->from($this->tableNamePrefix . '_node', 'n') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->leftJoin('h', $this->tableNamePrefix . '_restrictionrelation', 'r', 'r.originnodeaggregateid = n.nodeaggregateid AND r.contentstreamid = :contentStreamId AND r.affectednodeaggregateid = n.nodeaggregateid AND r.dimensionspacepointhash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') ->andWhere('h.contentstreamid = :contentStreamId') @@ -307,9 +312,10 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( DimensionSpacePointSet $dimensionSpacePointsToCheck ): DimensionSpacePointSet { $queryBuilder = $this->createQueryBuilder() - ->select('h.dimensionspacepoint, h.dimensionspacepointhash') + ->select('dsp.dimensionspacepoint, h.dimensionspacepointhash') ->from($this->tableNamePrefix . '_hierarchyrelation', 'h') ->innerJoin('h', $this->tableNamePrefix . '_node', 'n', 'n.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') @@ -369,10 +375,11 @@ public function getSubgraphs(): array private function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId): QueryBuilder { return $this->createQueryBuilder() - ->select('cn.*, ch.name, ch.contentstreamid, ch.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') + ->select('cn.*, ch.name, ch.contentstreamid, cdsp.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') ->from($this->tableNamePrefix . '_node', 'pn') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->tableNamePrefix . '_dimensionspacepoints', 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->leftJoin('pn', $this->tableNamePrefix . '_restrictionrelation', 'r', 'r.originnodeaggregateid = pn.nodeaggregateid AND r.contentstreamid = :contentStreamId AND r.affectednodeaggregateid = pn.nodeaggregateid AND r.dimensionspacepointhash = ph.dimensionspacepointhash') ->where('pn.nodeaggregateid = :parentNodeAggregateId') diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePoints.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePoints.php new file mode 100644 index 00000000000..097877c56e6 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePoints.php @@ -0,0 +1,100 @@ +databaseConnection->executeStatement( + 'INSERT INTO ' . $this->tableNamePrefix . '_dimensionspacepoints (hash, dimensionspacepoint) VALUES (:dimensionspacepointhash, :dimensionspacepoint)', + [ + 'dimensionspacepointhash' => $dimensionSpacePoint->hash, + 'dimensionspacepoint' => $dimensionSpacePoint->toJson() + ] + ); + } catch (UniqueConstraintViolationException $_) { + + } + } + + /** + * @param string $hash + * @param array $dimensionSpacePointCoordinates + * @return void + * @throws \Doctrine\DBAL\Exception + */ + public function insertDimensionSpacePointByHashAndCoordinates(string $hash, array $dimensionSpacePointCoordinates): void + { + try { + $this->databaseConnection->executeStatement( + 'INSERT INTO ' . $this->tableNamePrefix . '_dimensionspacepoints (hash, dimensionspacepoint) VALUES (:dimensionspacepointhash, :dimensionspacepoint)', + [ + 'dimensionspacepointhash' => $hash, + 'dimensionspacepoint' => json_encode($dimensionSpacePointCoordinates, JSON_THROW_ON_ERROR) + ] + ); + } catch (UniqueConstraintViolationException $_) { + + } + } + + public function getDimensionSpacePointByHash(string $hash): DimensionSpacePoint + { + if (!isset($this->dimensionspacePoints[$hash])) { + $this->fillInternalIndex(); + } + + return DimensionSpacePoint::fromJsonString($this->dimensionspacePoints[$hash]); + } + + public function getOriginDimensionSpacePointByHash(string $hash): OriginDimensionSpacePoint + { + if (!isset($this->dimensionspacePoints[$hash])) { + $this->fillInternalIndex(); + } + + return OriginDimensionSpacePoint::fromJsonString($this->dimensionspacePoints[$hash]); + } + + private function fillInternalIndex(): void + { + $allDimensionSpacePoints = $this->databaseConnection->fetchAllAssociativeIndexed('SELECT hash,dimensionspacepoint FROM ' . $this->tableNamePrefix . '_dimensionspacepoints'); + $this->dimensionspacePoints = array_map(static fn ($item) => $item['dimensionspacepoint'], $allDimensionSpacePoints); + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php index c93abc75f93..5f2bb5c774a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php @@ -51,7 +51,8 @@ final class NodeFactory public function __construct( private readonly ContentRepositoryId $contentRepositoryId, private readonly NodeTypeManager $nodeTypeManager, - private readonly PropertyConverter $propertyConverter + private readonly PropertyConverter $propertyConverter, + private readonly DimensionSpacePoints $dimensionSpacePointRepository ) { } @@ -75,7 +76,7 @@ public function mapNodeRowToNode( $visibilityConstraints ), NodeAggregateId::fromString($nodeRow['nodeaggregateid']), - OriginDimensionSpacePoint::fromJsonString($nodeRow['origindimensionspacepoint']), + $this->dimensionSpacePointRepository->getOriginDimensionSpacePointByHash($nodeRow['origindimensionspacepointhash']), NodeAggregateClassification::from($nodeRow['classification']), NodeTypeName::fromString($nodeRow['nodetypename']), $nodeType, @@ -161,9 +162,7 @@ public function mapNodeRowsToNodeAggregate( foreach ($nodeRows as $nodeRow) { // A node can occupy exactly one DSP and cover multiple ones... - $occupiedDimensionSpacePoint = OriginDimensionSpacePoint::fromJsonString( - $nodeRow['origindimensionspacepoint'] - ); + $occupiedDimensionSpacePoint = $this->dimensionSpacePointRepository->getOriginDimensionSpacePointByHash($nodeRow['origindimensionspacepointhash']); if (!isset($nodesByOccupiedDimensionSpacePoints[$occupiedDimensionSpacePoint->hash])) { // ... so we handle occupation exactly once ... $nodesByOccupiedDimensionSpacePoints[$occupiedDimensionSpacePoint->hash] = $this->mapNodeRowToNode( @@ -239,9 +238,7 @@ public function mapNodeRowsToNodeAggregates( foreach ($nodeRows as $nodeRow) { // A node can occupy exactly one DSP and cover multiple ones... $rawNodeAggregateId = $nodeRow['nodeaggregateid']; - $occupiedDimensionSpacePoint = OriginDimensionSpacePoint::fromJsonString( - $nodeRow['origindimensionspacepoint'] - ); + $occupiedDimensionSpacePoint = $this->dimensionSpacePointRepository->getOriginDimensionSpacePointByHash($nodeRow['origindimensionspacepointhash']); if ( !isset($nodesByOccupiedDimensionSpacePointsByNodeAggregate [$rawNodeAggregateId][$occupiedDimensionSpacePoint->hash]) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index 00d5356a176..865d35ad4a5 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -71,10 +71,11 @@ public function findParentNode( : $originDimensionSpacePoint->hash ]; $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT p.*, ph.contentstreamid, ph.name FROM ' . $this->tableNamePrefix . '_node p + 'SELECT p.*, ph.contentstreamid, ph.name, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node p INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ph ON ph.childnodeanchor = p.relationanchorpoint INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ch ON ch.parentnodeanchor = p.relationanchorpoint INNER JOIN ' . $this->tableNamePrefix . '_node c ON ch.childnodeanchor = c.relationanchorpoint + INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON p.origindimensionspacepointhash = dsp.hash WHERE c.nodeaggregateid = :childNodeAggregateId AND c.origindimensionspacepointhash = :originDimensionSpacePointHash AND ph.contentstreamid = :contentStreamId @@ -101,8 +102,9 @@ public function findNodeInAggregate( DimensionSpacePoint $coveredDimensionSpacePoint ): ?NodeRecord { $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT n.*, h.name FROM ' . $this->tableNamePrefix . '_node n + 'SELECT n.*, h.name, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -129,8 +131,9 @@ public function findNodeByIds( OriginDimensionSpacePoint $originDimensionSpacePoint ): ?NodeRecord { $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT n.*, h.name FROM ' . $this->tableNamePrefix . '_node n + 'SELECT n.*, h.name, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.nodeaggregateid = :nodeAggregateId AND n.origindimensionspacepointhash = :originDimensionSpacePointHash AND h.contentstreamid = :contentStreamId', @@ -216,7 +219,8 @@ public function getAnchorPointsForNodeAggregateInContentStream( public function getNodeByAnchorPoint(NodeRelationAnchorPoint $nodeRelationAnchorPoint): ?NodeRecord { $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT n.* FROM ' . $this->tableNamePrefix . '_node n + 'SELECT n.*, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n + INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.relationanchorpoint = :relationAnchorPoint', [ 'relationAnchorPoint' => $nodeRelationAnchorPoint->value, @@ -648,12 +652,14 @@ public function findDescendantNodeAggregateIds( */ protected function mapRawDataToHierarchyRelation(array $rawData): HierarchyRelation { + $dimensionspacepointRaw = $this->client->getConnection()->fetchOne('SELECT dimensionspacepoint FROM ' . $this->tableNamePrefix . '_dimensionspacepoints WHERE hash = :hash', ['hash' => $rawData['dimensionspacepointhash']]); + return new HierarchyRelation( NodeRelationAnchorPoint::fromInteger((int)$rawData['parentnodeanchor']), NodeRelationAnchorPoint::fromInteger((int)$rawData['childnodeanchor']), $rawData['name'] ? NodeName::fromString($rawData['name']) : null, ContentStreamId::fromString($rawData['contentstreamid']), - DimensionSpacePoint::fromJsonString($rawData['dimensionspacepoint']), + DimensionSpacePoint::fromJsonString($dimensionspacepointRaw), $rawData['dimensionspacepointhash'], (int)$rawData['position'] );