Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: FEATURE: Rework CR CatchUp mechanism #4988

Draft
wants to merge 34 commits into
base: 9.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3a2fc46
WIP: FEATURE: Rework CR CatchUp mechanism
bwaidelich Apr 11, 2024
99cd1e0
Remove `react/async` dependency
bwaidelich Apr 12, 2024
e8a0bdd
Remove `react/async` dependency 2/2
bwaidelich Apr 12, 2024
d2382ee
Cosmetic fix to satisfy linter
bwaidelich Apr 13, 2024
7dc35fc
Remove unused namespace imports
bwaidelich Apr 13, 2024
13e1238
Replace `CheckpointStorageInterface`
bwaidelich Apr 14, 2024
4c425cd
Replace `CheckpointStorageInterface`
bwaidelich Apr 14, 2024
b1d28f6
Merge branch 'feature/4746-rework-catchup-mechanism' of https://githu…
bwaidelich Apr 14, 2024
ad57a91
Remove `DbalClientInterface` and implementations
bwaidelich Apr 15, 2024
b817b1d
Update composer manifest
bwaidelich Apr 16, 2024
cb653fd
Tweak checkpoint error message
bwaidelich Apr 16, 2024
43da4c2
Re-add `neos/eventstore-doctrineadapter` dependency
bwaidelich Apr 16, 2024
bb1c498
Mostly cosmetic tweaks to satisfy linter
bwaidelich Apr 16, 2024
e62cb87
Make checkpoint table check a platform specific check
bwaidelich Apr 17, 2024
7cc13ed
Remove notion of "sync" from composer scripts and readme
bwaidelich Apr 17, 2024
fc6ec5c
Remove ProjectionCatchUpTriggerInterface and implementations
bwaidelich Apr 17, 2024
89eada5
Merge branch '9.0' into feature/4746-rework-catchup-mechanism
bwaidelich Apr 24, 2024
cc3a191
Merge branch '9.0' into feature/4746-rework-catchup-mechanism
bwaidelich Apr 25, 2024
7c8ae2c
Merge branch '9.0' into feature/4746-rework-catchup-mechanism
bwaidelich Apr 26, 2024
ed47eb6
Fix ContentStreamProjection checkpoint
bwaidelich Apr 26, 2024
592e78d
Merge branch '9.0' into feature/4746-rework-catchup-mechanism
bwaidelich Apr 30, 2024
4af1e3c
Merge branch '9.0' into feature/4746-rework-catchup-mechanism
bwaidelich May 1, 2024
4c68ca7
BUGFIX: Remove doctrine migrate and cr setup commands from behat scripts
bwaidelich May 1, 2024
7bfd941
Use dedicated connection for EventStore
bwaidelich May 1, 2024
e2e8af6
Re-use previously created event store instances
bwaidelich May 1, 2024
fb9b897
Merge branch '9.0' into feature/4746-rework-catchup-mechanism
bwaidelich May 6, 2024
082c5ca
Merge remote-tracking branch 'origin/9.0' into feature/4746-rework-ca…
mhsdesign May 17, 2024
adf6eef
TASK: Adjustments after merging 9.0 (beta10) to satisfy phpstan mostly
mhsdesign May 17, 2024
c6920ec
Merge remote-tracking branch 'origin/9.0' into feature/4746-rework-ca…
mhsdesign May 17, 2024
88abe17
Merge branch '9.0' into feature/4746-rework-catchup-mechanism
bwaidelich May 19, 2024
27f330f
Merge branch 'task/remove-dbal-client' into feature/4746-rework-catch…
bwaidelich May 19, 2024
b4fe6e9
Merge branch '9.0' into feature/4746-rework-catchup-mechanism
bwaidelich May 19, 2024
3abc8f0
Make ContentGraphTableNames::tableNamePrefix private again
bwaidelich May 19, 2024
3d6edc7
Merge branch '9.0' into feature/4746-rework-catchup-mechanism
bwaidelich May 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 0 additions & 2 deletions .composer.json
Expand Up @@ -42,7 +42,6 @@
"@test:behat-cli -c Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/behat.yml.dist",
"@test:behat-cli -c Neos.ContentRepository.Export/Tests/Behavior/behat.yml.dist",
"@test:behat-cli -c Neos.TimeableNodeVisibility/Tests/Behavior/behat.yml.dist",
"../../flow doctrine:migrate --quiet; ../../flow cr:setup",
"@test:behat-cli -c Neos.Neos/Tests/Behavior/behat.yml"
],
"test:behavioral:stop-on-failure": [
Expand All @@ -51,7 +50,6 @@
"@test:behat-cli -vvv --stop-on-failure -c Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/behat.yml.dist",
"@test:behat-cli -vvv --stop-on-failure -c Neos.ContentRepository.Export/Tests/Behavior/behat.yml.dist",
"@test:behat-cli -vvv --stop-on-failure -c Neos.TimeableNodeVisibility/Tests/Behavior/behat.yml.dist",
"../../flow doctrine:migrate --quiet; ../../flow cr:setup",
"@test:behat-cli -vvv --stop-on-failure -c Neos.Neos/Tests/Behavior/behat.yml"
],
"test": [
Expand Down
3 changes: 1 addition & 2 deletions Neos.ContentGraph.DoctrineDbalAdapter/composer.json
Expand Up @@ -10,8 +10,7 @@
"license": "GPL-3.0+",
"require": {
"neos/contentrepository-core": "self.version",
"doctrine/dbal": "^2.13",
"doctrine/migrations": "*"
"neos/contentrepository-dbaltools": "*"
},
"autoload": {
"psr-4": {
Expand Down
Expand Up @@ -6,7 +6,6 @@

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Types\Types;
use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeMove;
use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeRemoval;
use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeVariation;
Expand Down Expand Up @@ -46,7 +45,6 @@
use Neos\ContentRepository\Core\Infrastructure\DbalCheckpointStorage;
use Neos\ContentRepository\Core\Infrastructure\DbalSchemaDiff;
use Neos\ContentRepository\Core\NodeType\NodeTypeName;
use Neos\ContentRepository\Core\Projection\CheckpointStorageStatusType;
use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTags;
use Neos\ContentRepository\Core\Projection\ContentGraph\Timestamps;
use Neos\ContentRepository\Core\Projection\ProjectionInterface;
Expand All @@ -56,6 +54,8 @@
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\Node\NodeName;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepository\DbalTools\CheckpointHelper;
use Neos\ContentRepository\DbalTools\DbalSchemaDiff;
use Neos\EventStore\Model\Event\SequenceNumber;
use Neos\EventStore\Model\EventEnvelope;

Expand All @@ -73,20 +73,13 @@ final class DoctrineDbalContentGraphProjection implements ProjectionInterface, W

public const RELATION_DEFAULT_OFFSET = 128;

private DbalCheckpointStorage $checkpointStorage;

public function __construct(
private readonly Connection $dbal,
private readonly ProjectionContentGraph $projectionContentGraph,
private readonly ContentGraphTableNames $tableNames,
private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository,
private readonly ContentGraphFinder $contentGraphFinder
) {
$this->checkpointStorage = new DbalCheckpointStorage(
$this->dbal,
$this->tableNames->checkpoint(),
self::class
);
}

protected function getProjectionContentGraph(): ProjectionContentGraph
Expand All @@ -97,9 +90,8 @@ protected function getProjectionContentGraph(): ProjectionContentGraph
public function setUp(): void
{
foreach ($this->determineRequiredSqlStatements() as $statement) {
$this->getDatabaseConnection()->executeStatement($statement);
$this->dbal->executeStatement($statement);
}
$this->checkpointStorage->setUp();
}

/**
Expand All @@ -117,15 +109,8 @@ private function determineRequiredSqlStatements(): array

public function status(): ProjectionStatus
{
$checkpointStorageStatus = $this->checkpointStorage->status();
if ($checkpointStorageStatus->type === CheckpointStorageStatusType::ERROR) {
return ProjectionStatus::error($checkpointStorageStatus->details);
}
if ($checkpointStorageStatus->type === CheckpointStorageStatusType::SETUP_REQUIRED) {
return ProjectionStatus::setupRequired($checkpointStorageStatus->details);
}
try {
$this->getDatabaseConnection()->connect();
$this->dbal->connect();
} catch (\Throwable $e) {
return ProjectionStatus::error(sprintf('Failed to connect to database: %s', $e->getMessage()));
}
Expand All @@ -137,57 +122,33 @@ public function status(): ProjectionStatus
if ($requiredSqlStatements !== []) {
return ProjectionStatus::setupRequired(sprintf('The following SQL statement%s required: %s', count($requiredSqlStatements) !== 1 ? 's are' : ' is', implode(chr(10), $requiredSqlStatements)));
}
try {
$this->getCheckpoint();
} catch (\Exception $exception) {
return ProjectionStatus::error('Error while retrieving checkpoint: ' . $exception->getMessage());
}
return ProjectionStatus::ok();
}

public function reset(): void
{
$this->truncateDatabaseTables();

$this->checkpointStorage->acquireLock();
$this->checkpointStorage->updateAndReleaseLock(SequenceNumber::none());
$this->getState()->forgetInstances();
}

public function markStale(): void
{
$this->getState()->forgetInstances();
}

private function truncateDatabaseTables(): void
{
$this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->node());
$this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->hierarchyRelation());
$this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->referenceRelation());
$this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->dimensionSpacePoints());
CheckpointHelper::resetCheckpoint($this->dbal, $this->tableNames->checkpoint());
$this->getState()->forgetInstances();
}

public function canHandle(EventInterface $event): bool
public function markStale(): void
{
return in_array($event::class, [
RootNodeAggregateWithNodeWasCreated::class,
RootNodeAggregateDimensionsWereUpdated::class,
NodeAggregateWithNodeWasCreated::class,
NodeAggregateNameWasChanged::class,
ContentStreamWasForked::class,
ContentStreamWasRemoved::class,
NodePropertiesWereSet::class,
NodeReferencesWereSet::class,
NodeAggregateTypeWasChanged::class,
DimensionSpacePointWasMoved::class,
DimensionShineThroughWasAdded::class,
NodeAggregateWasRemoved::class,
NodeAggregateWasMoved::class,
NodeSpecializationVariantWasCreated::class,
NodeGeneralizationVariantWasCreated::class,
NodePeerVariantWasCreated::class,
SubtreeWasTagged::class,
SubtreeWasUntagged::class,
]);
$this->getState()->forgetInstances();
}


public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void
{
$this->dbal->beginTransaction();
match ($event::class) {
RootNodeAggregateWithNodeWasCreated::class => $this->whenRootNodeAggregateWithNodeWasCreated($event, $eventEnvelope),
RootNodeAggregateDimensionsWereUpdated::class => $this->whenRootNodeAggregateDimensionsWereUpdated($event),
Expand All @@ -207,13 +168,15 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void
NodePeerVariantWasCreated::class => $this->whenNodePeerVariantWasCreated($event, $eventEnvelope),
SubtreeWasTagged::class => $this->whenSubtreeWasTagged($event),
SubtreeWasUntagged::class => $this->whenSubtreeWasUntagged($event),
default => throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))),
default => null,
};
CheckpointHelper::updateCheckpoint($this->dbal, $this->tableNames->checkpoint(), $eventEnvelope->sequenceNumber);
$this->dbal->commit();
}

public function getCheckpointStorage(): DbalCheckpointStorage
public function getCheckpoint(): SequenceNumber
{
return $this->checkpointStorage;
return CheckpointHelper::getCheckpoint($this->dbal, $this->tableNames->checkpoint());
}

public function getState(): ContentGraphFinder
Expand Down Expand Up @@ -272,13 +235,13 @@ private function whenRootNodeAggregateDimensionsWereUpdated(RootNodeAggregateDim
}

// delete all hierarchy edges of the root node
$this->getDatabaseConnection()->executeUpdate('
DELETE FROM ' . $this->tableNames->hierarchyRelation() . '
WHERE
parentnodeanchor = :parentNodeAnchor
AND childnodeanchor = :childNodeAnchor
AND contentstreamid = :contentStreamId
', [
$this->dbal->executeUpdate('
DELETE FROM ' . $this->tableNames->hierarchyRelation() . '
WHERE
parentnodeanchor = :parentNodeAnchor
AND childnodeanchor = :childNodeAnchor
AND contentstreamid = :contentStreamId
', [
'parentNodeAnchor' => NodeRelationAnchorPoint::forRootEdge()->value,
'childNodeAnchor' => $rootNodeAnchorPoint->value,
'contentStreamId' => $event->contentStreamId->value,
Expand Down Expand Up @@ -353,7 +316,7 @@ private function createNodeWithHierarchy(
EventEnvelope $eventEnvelope,
): void {
$node = NodeRecord::createNewInDatabase(
$this->getDatabaseConnection(),
$this->dbal,
$this->tableNames,
$nodeAggregateId,
$originDimensionSpacePoint->jsonSerialize(),
Expand Down Expand Up @@ -442,7 +405,7 @@ private function connectHierarchy(
$inheritedSubtreeTags,
);

$hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->tableNames);
$hierarchyRelation->addToDatabase($this->dbal, $this->tableNames);
}
}

Expand Down Expand Up @@ -529,7 +492,7 @@ private function getRelationPositionAfterRecalculation(
$position = $offset;
$offset += self::RELATION_DEFAULT_OFFSET;
}
$relation->assignNewPosition($offset, $this->getDatabaseConnection(), $this->tableNames);
$relation->assignNewPosition($offset, $this->dbal, $this->tableNames);
}

return $position;
Expand All @@ -543,10 +506,11 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void
//
// 1) Copy HIERARCHY RELATIONS (this is the MAIN OPERATION here)
//
$this->getDatabaseConnection()->executeUpdate('
$this->dbal->executeUpdate('
INSERT INTO ' . $this->tableNames->hierarchyRelation() . ' (
parentnodeanchor,
childnodeanchor,

position,
dimensionspacepointhash,
subtreetags,
Expand All @@ -555,6 +519,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void
SELECT
h.parentnodeanchor,
h.childnodeanchor,

h.position,
h.dimensionspacepointhash,
h.subtreetags,
Expand All @@ -565,7 +530,6 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void
', [
'sourceContentStreamId' => $event->sourceContentStreamId->value
]);

// 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).
}
Expand Down
Expand Up @@ -8,7 +8,8 @@
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Neos\ContentRepository\Core\Infrastructure\DbalSchemaFactory;
use Neos\ContentRepository\DbalTools\CheckpointHelper;
use Neos\ContentRepository\DbalTools\DbalSchemaFactory;

/**
* @internal
Expand All @@ -28,7 +29,8 @@ public function buildSchema(AbstractSchemaManager $schemaManager): Schema
$this->createNodeTable(),
$this->createHierarchyRelationTable(),
$this->createReferenceRelationTable(),
$this->createDimensionSpacePointsTable()
$this->createDimensionSpacePointsTable(),
CheckpointHelper::checkpointTableSchema($this->contentGraphTableNames->checkpoint()),
]);
}

Expand Down
Expand Up @@ -26,7 +26,6 @@
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet;
use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\Node\NodeName;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;

/**
Expand Down
2 changes: 1 addition & 1 deletion Neos.ContentGraph.PostgreSQLAdapter/composer.json
Expand Up @@ -11,7 +11,7 @@
"require": {
"neos/contentrepository-core": "self.version",
"doctrine/dbal": "*",
"doctrine/migrations": "*"
"neos/contentrepository-dbaltools": "*"
},
"autoload": {
"psr-4": {
Expand Down