Skip to content

Commit 9864b2f

Browse files
authored
Merge pull request #3849 from neos/referenceProperties
ES CR: Reference properties
2 parents 56aef2b + 8eb8527 commit 9864b2f

File tree

72 files changed

+2003
-1095
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+2003
-1095
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Neos\Flow\Persistence\Doctrine\Migrations;
6+
7+
use Doctrine\DBAL\DBALException;
8+
use Doctrine\Migrations\Exception\AbortMigration;
9+
use Doctrine\Migrations\AbstractMigration;
10+
use Doctrine\DBAL\Schema\Schema;
11+
12+
class Version20220721154646 extends AbstractMigration
13+
{
14+
public function getDescription(): string
15+
{
16+
return 'Add properties to reference relations';
17+
}
18+
19+
/**
20+
* @param Schema $schema
21+
* @throws DBALException
22+
* @throws AbortMigration
23+
*/
24+
public function up(Schema $schema): void
25+
{
26+
$this->abortIf(
27+
$this->connection->getDatabasePlatform()->getName() != 'mysql',
28+
'Migration can only be executed safely on "mysql".'
29+
);
30+
31+
$this->addSql('ALTER TABLE neos_contentgraph_referencerelation ADD properties LONGTEXT NULL');
32+
33+
}
34+
35+
/**
36+
* @throws AbortMigration
37+
* @throws DBALException
38+
*/
39+
public function down(Schema $schema): void
40+
{
41+
$this->abortIf(
42+
$this->connection->getDatabasePlatform()->getName() != 'mysql',
43+
'Migration can only be executed safely on "mysql".'
44+
);
45+
46+
$this->addSql('ALTER TABLE neos_contentgraph_referencerelation DROP properties');
47+
}
48+
}

Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ private function findRelationAnchorPointByIdentifiers(
264264
'dimensionSpacePointHash' => $dimensionSpacePoint->hash,
265265
'nodeAggregateIdentifier' => (string)$nodeAggregateIdentifier
266266
]
267-
)->fetch();
267+
)->fetchAssociative();
268268

269269
return $nodeRecord['relationanchorpoint'];
270270
}

Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/ReferenceIntegrityIsProvided.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ Feature: Run integrity violation detection regarding reference relations
5656
| contentStreamIdentifier | "cs-identifier" |
5757
| sourceOriginDimensionSpacePoint | {"language":"de"} |
5858
| sourceNodeAggregateIdentifier | "source-nodandaise" |
59-
| destinationNodeAggregateIdentifiers | ["anthony-destinode"] |
6059
| referenceName | "referenceProperty" |
60+
| references | [{"target": "anthony-destinode"}] |
6161
| initiatingUserIdentifier | "00000000-0000-0000-0000-000000000000" |
6262
And the graph projection is fully up to date
6363
And I detach the following reference relation from its source:

Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/GraphProjector.php

Lines changed: 67 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ public function whenContentStreamWasForked(ContentStreamWasForked $event): void
480480
h.position,
481481
h.dimensionspacepoint,
482482
h.dimensionspacepointhash,
483-
"' . (string)$event->getContentStreamIdentifier() . '" AS contentstreamidentifier
483+
"' . $event->getContentStreamIdentifier() . '" AS contentstreamidentifier
484484
FROM
485485
neos_contentgraph_hierarchyrelation h
486486
WHERE h.contentstreamidentifier = :sourceContentStreamIdentifier
@@ -499,7 +499,7 @@ public function whenContentStreamWasForked(ContentStreamWasForked $event): void
499499
affectednodeaggregateidentifier
500500
)
501501
SELECT
502-
"' . (string)$event->getContentStreamIdentifier() . '" AS contentstreamidentifier,
502+
"' . $event->getContentStreamIdentifier() . '" AS contentstreamidentifier,
503503
r.dimensionspacepointhash,
504504
r.originnodeaggregateidentifier,
505505
r.affectednodeaggregateidentifier
@@ -579,32 +579,58 @@ public function whenNodePropertiesWereSet(NodePropertiesWereSet $event): void
579579
public function whenNodeReferencesWereSet(NodeReferencesWereSet $event): void
580580
{
581581
$this->transactional(function () use ($event) {
582-
$this->updateNodeWithCopyOnWrite($event, function (NodeRecord $node) {
583-
});
582+
foreach ($event->affectedSourceOriginDimensionSpacePoints as $originDimensionSpacePoint) {
583+
$nodeAnchorPoint = $this->projectionContentGraph
584+
->getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStream(
585+
$event->sourceNodeAggregateIdentifier,
586+
$originDimensionSpacePoint,
587+
$event->contentStreamIdentifier
588+
);
584589

585-
$nodeAnchorPoint = $this->projectionContentGraph
586-
->getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStream(
587-
$event->sourceNodeAggregateIdentifier,
588-
$event->sourceOriginDimensionSpacePoint,
589-
$event->contentStreamIdentifier
590+
if (is_null($nodeAnchorPoint)) {
591+
throw new \InvalidArgumentException(
592+
'Could not apply event of type "' . get_class($event)
593+
. '" since no anchor point could be resolved for node '
594+
. $event->getNodeAggregateIdentifier() . ' in content stream '
595+
. $event->getContentStreamIdentifier(),
596+
1658580583
597+
);
598+
}
599+
600+
$this->updateNodeRecordWithCopyOnWrite(
601+
$event->contentStreamIdentifier,
602+
$nodeAnchorPoint,
603+
function (NodeRecord $node) {
604+
}
590605
);
591606

592-
// remove old
593-
$this->getDatabaseConnection()->delete('neos_contentgraph_referencerelation', [
594-
'nodeanchorpoint' => $nodeAnchorPoint,
595-
'name' => $event->referenceName
596-
]);
607+
$nodeAnchorPoint = $this->projectionContentGraph
608+
->getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStream(
609+
$event->sourceNodeAggregateIdentifier,
610+
$originDimensionSpacePoint,
611+
$event->contentStreamIdentifier
612+
);
597613

598-
// set new
599-
$position = 0;
600-
foreach ($event->destinationNodeAggregateIdentifiers as $destinationNodeIdentifier) {
601-
$this->getDatabaseConnection()->insert('neos_contentgraph_referencerelation', [
602-
'name' => $event->referenceName,
603-
'position' => $position,
614+
// remove old
615+
$this->getDatabaseConnection()->delete('neos_contentgraph_referencerelation', [
604616
'nodeanchorpoint' => $nodeAnchorPoint,
605-
'destinationnodeaggregateidentifier' => $destinationNodeIdentifier,
617+
'name' => $event->referenceName
606618
]);
607-
$position++;
619+
620+
// set new
621+
$position = 0;
622+
foreach ($event->references as $reference) {
623+
$this->getDatabaseConnection()->insert('neos_contentgraph_referencerelation', [
624+
'name' => $event->referenceName,
625+
'position' => $position,
626+
'nodeanchorpoint' => $nodeAnchorPoint,
627+
'destinationnodeaggregateidentifier' => $reference->targetNodeAggregateIdentifier,
628+
'properties' => $reference->properties
629+
? \json_encode($reference->properties, JSON_THROW_ON_ERROR)
630+
: null
631+
]);
632+
$position++;
633+
}
608634
}
609635
});
610636
}
@@ -789,37 +815,26 @@ function (NodeRecord $node) use ($event) {
789815
*/
790816
protected function updateNodeWithCopyOnWrite(DomainEventInterface $event, callable $operations): mixed
791817
{
792-
switch (get_class($event)) {
793-
case NodeReferencesWereSet::class:
794-
/** @var NodeReferencesWereSet $event */
795-
$anchorPoint = $this->projectionContentGraph
796-
->getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStream(
797-
$event->sourceNodeAggregateIdentifier,
798-
$event->sourceOriginDimensionSpacePoint,
799-
$event->contentStreamIdentifier
800-
);
801-
break;
802-
default:
803-
if (
804-
method_exists($event, 'getNodeAggregateIdentifier')
805-
&& method_exists($event, 'getOriginDimensionSpacePoint')
806-
&& method_exists($event, 'getContentStreamIdentifier')
807-
) {
808-
$anchorPoint = $this->projectionContentGraph
809-
->getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStream(
810-
$event->getNodeAggregateIdentifier(),
811-
$event->getOriginDimensionSpacePoint(),
812-
$event->getContentStreamIdentifier()
813-
);
814-
} else {
815-
throw new \InvalidArgumentException(
816-
'Cannot update node with copy on write for events of type '
817-
. get_class($event) . ' since they provide no NodeAggregateIdentifier, '
818-
. 'OriginDimensionSpacePoint or ContentStreamIdentifier',
819-
1645303167
820-
);
821-
}
818+
if (
819+
method_exists($event, 'getNodeAggregateIdentifier')
820+
&& method_exists($event, 'getOriginDimensionSpacePoint')
821+
&& method_exists($event, 'getContentStreamIdentifier')
822+
) {
823+
$anchorPoint = $this->projectionContentGraph
824+
->getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStream(
825+
$event->getNodeAggregateIdentifier(),
826+
$event->getOriginDimensionSpacePoint(),
827+
$event->getContentStreamIdentifier()
828+
);
829+
} else {
830+
throw new \InvalidArgumentException(
831+
'Cannot update node with copy on write for events of type '
832+
. get_class($event) . ' since they provide no NodeAggregateIdentifier, '
833+
. 'OriginDimensionSpacePoint or ContentStreamIdentifier',
834+
1645303167
835+
);
822836
}
837+
823838
if (is_null($anchorPoint)) {
824839
throw new \InvalidArgumentException(
825840
'Cannot update node with copy on write since no anchor point could be resolved for node '

Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public function hierarchyIntegrityIsProvided(): Result
6666
$invalidlyHashedHierarchyRelationRecords = $this->client->getConnection()->executeQuery(
6767
'SELECT * FROM neos_contentgraph_hierarchyrelation
6868
WHERE dimensionspacepointhash != MD5(dimensionspacepoint)'
69-
)->fetchAll();
69+
)->fetchAllAssociative();
7070

7171
foreach ($invalidlyHashedHierarchyRelationRecords as $record) {
7272
$result->addError(new Error(
@@ -88,7 +88,7 @@ public function hierarchyIntegrityIsProvided(): Result
8888
[
8989
'rootNodeAnchor' => NodeRelationAnchorPoint::forRootEdge()
9090
]
91-
)->fetchAll();
91+
)->fetchAllAssociative();
9292

9393
foreach ($hierarchyRelationRecordsAppearingMultipleTimes as $record) {
9494
$result->addError(new Error(
@@ -123,7 +123,7 @@ public function siblingsAreDistinctlySorted(): Result
123123
[
124124
'relationAnchorPoint' => $hierarchyRelationRecord['childnodeanchor']
125125
]
126-
)->fetchAll();
126+
)->fetchAllAssociative();
127127

128128
$result->addError(new Error(
129129
'Siblings ' . implode(', ', array_map(function (array $record) {
@@ -153,7 +153,7 @@ public function tetheredNodesAreNamed(): Result
153153
[
154154
'tethered' => NodeAggregateClassification::CLASSIFICATION_TETHERED->value
155155
]
156-
)->fetchAll();
156+
)->fetchAllAssociative();
157157

158158
foreach ($unnamedTetheredNodeRecords as $unnamedTetheredNodeRecord) {
159159
$result->addError(new Error(
@@ -189,7 +189,7 @@ public function restrictionsArePropagatedRecursively(): Result
189189
AND cr.contentstreamidentifier = h.contentstreamidentifier
190190
AND cr.dimensionspacepointhash = h.dimensionspacepointhash
191191
WHERE cr.affectednodeaggregateidentifier IS NULL'
192-
)->fetchAll();
192+
)->fetchAllAssociative();
193193

194194
foreach ($nodeRecordsWithMissingRestrictions as $nodeRecord) {
195195
$result->addError(new Error(
@@ -227,7 +227,7 @@ public function restrictionIntegrityIsProvided(): Result
227227
AND ch.dimensionspacepointhash = r.dimensionspacepointhash
228228
WHERE p.nodeaggregateidentifier IS NULL
229229
OR c.nodeaggregateidentifier IS NULL'
230-
)->fetchAll();
230+
)->fetchAllAssociative();
231231

232232
foreach ($restrictionRelationRecordsWithoutOriginOrAffectedNode as $relationRecord) {
233233
$result->addError(new Error(
@@ -254,7 +254,7 @@ public function referenceIntegrityIsProvided(): Result
254254
WHERE nodeanchorpoint NOT IN (
255255
SELECT relationanchorpoint FROM neos_contentgraph_node
256256
)'
257-
)->fetchAll();
257+
)->fetchAllAssociative();
258258

259259
foreach ($referenceRelationRecordsDetachedFromSource as $record) {
260260
$result->addError(new Error(
@@ -279,7 +279,7 @@ public function referenceIntegrityIsProvided(): Result
279279
AND sh.dimensionspacepointhash = dh.dimensionspacepointhash
280280
WHERE d.nodeaggregateidentifier IS NULL
281281
GROUP BY s.nodeaggregateidentifier'
282-
)->fetchAll();
282+
)->fetchAllAssociative();
283283

284284
foreach ($referenceRelationRecordsWithInvalidTarget as $record) {
285285
$result->addError(new Error(
@@ -348,7 +348,7 @@ public function allNodesAreConnectedToARootNodePerSubgraph(): Result
348348
'contentStreamIdentifier' => (string)$contentStreamIdentifier,
349349
'dimensionSpacePointHash' => $dimensionSpacePoint->hash
350350
]
351-
)->fetchAll();
351+
)->fetchAllAssociative();
352352

353353
if (!empty($nodeAggregateIdentifiersInCycles)) {
354354
$nodeAggregateIdentifiersInCycles = array_map(function (array $record) {
@@ -400,7 +400,7 @@ public function nodeAggregateIdentifiersAreUniquePerSubgraph(): Result
400400
'contentStreamIdentifier' => (string)$contentStreamIdentifier,
401401
'dimensionSpacePointHash' => $dimensionSpacePoint->hash
402402
]
403-
)->fetchAll();
403+
)->fetchAllAssociative();
404404

405405
foreach ($ambiguousNodeAggregateRecords as $ambiguousRecord) {
406406
$result->addError(new Error(
@@ -436,7 +436,7 @@ public function allNodesHaveAtMostOneParentPerSubgraph(): Result
436436
'contentStreamIdentifier' => (string)$contentStreamIdentifier,
437437
'dimensionSpacePointHash' => $dimensionSpacePoint->hash
438438
]
439-
)->fetchAll();
439+
)->fetchAllAssociative();
440440

441441
foreach ($nodeRecordsWithMultipleParents as $record) {
442442
$result->addError(new Error(
@@ -473,7 +473,7 @@ public function nodeAggregatesAreConsistentlyTypedPerContentStream(): Result
473473
'contentStreamIdentifier' => (string)$contentStreamIdentifier,
474474
'nodeAggregateIdentifier' => (string)$nodeAggregateIdentifier
475475
]
476-
)->fetchAll();
476+
)->fetchAllAssociative();
477477

478478
if (count($nodeAggregateRecords) > 1) {
479479
$result->addError(new Error(
@@ -515,7 +515,7 @@ public function nodeAggregatesAreConsistentlyClassifiedPerContentStream(): Resul
515515
'contentStreamIdentifier' => (string)$contentStreamIdentifier,
516516
'nodeAggregateIdentifier' => (string)$nodeAggregateIdentifier
517517
]
518-
)->fetchAll();
518+
)->fetchAllAssociative();
519519

520520
if (count($nodeAggregateRecords) > 1) {
521521
$result->addError(new Error(
@@ -554,7 +554,7 @@ public function childNodeCoverageIsASubsetOfParentNodeCoverage(): Result
554554
[
555555
'contentStreamIdentifier' => (string)$contentStreamIdentifier
556556
]
557-
)->fetchAll();
557+
)->fetchAllAssociative();
558558

559559
foreach ($excessivelyCoveringNodeRecords as $excessivelyCoveringNodeRecord) {
560560
$result->addError(new Error(
@@ -597,7 +597,7 @@ public function allNodesCoverTheirOrigin(): Result
597597
'contentStreamIdentifier' => (string)$contentStreamIdentifier,
598598
'rootClassification' => NodeAggregateClassification::CLASSIFICATION_ROOT->value
599599
]
600-
)->fetchAll();
600+
)->fetchAllAssociative();
601601

602602
foreach ($nodeRecordsWithMissingOriginCoverage as $nodeRecord) {
603603
$result->addError(new Error(
@@ -624,7 +624,7 @@ protected function findProjectedContentStreamIdentifiers(): iterable
624624

625625
$rows = $connection->executeQuery(
626626
'SELECT DISTINCT contentstreamidentifier FROM neos_contentgraph_hierarchyrelation'
627-
)->fetchAll();
627+
)->fetchAllAssociative();
628628

629629
return array_map(function (array $row) {
630630
return ContentStreamIdentifier::fromString($row['contentstreamidentifier']);
@@ -640,7 +640,7 @@ protected function findProjectedDimensionSpacePoints(): DimensionSpacePointSet
640640
{
641641
$records = $this->client->getConnection()->executeQuery(
642642
'SELECT DISTINCT dimensionspacepoint FROM neos_contentgraph_hierarchyrelation'
643-
)->fetchAll();
643+
)->fetchAllAssociative();
644644

645645
$records = array_map(function (array $record) {
646646
return DimensionSpacePoint::fromJsonString($record['dimensionspacepoint']);

0 commit comments

Comments
 (0)