From 6bef019becff102a89a273b24b067eed169839a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Thu, 30 Nov 2023 18:34:38 +0100 Subject: [PATCH] TASK: Normalize DimensionSpacePoints to separate table This should reduce stored data amounts, also make queries and workspace copies faster. The hierarchy relations are now entirely limited size field types and should not need external lookup like "text" fields do. --- .../DoctrineDbalContentGraphProjection.php | 15 +-- ...trineDbalContentGraphProjectionFactory.php | 6 +- .../DoctrineDbalContentGraphSchemaBuilder.php | 17 ++- .../Domain/Projection/HierarchyRelation.php | 5 +- .../src/Domain/Projection/NodeRecord.php | 7 +- .../ProjectionIntegrityViolationDetector.php | 18 ++-- .../src/Domain/Repository/ContentGraph.php | 21 ++-- .../Repository/DimensionSpacePoints.php | 100 ++++++++++++++++++ .../src/Domain/Repository/NodeFactory.php | 13 +-- .../Repository/ProjectionContentGraph.php | 16 ++- 10 files changed, 174 insertions(+), 44 deletions(-) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePoints.php 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'] );