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

FEATURE: ContentGraphAdapter for write side access #4979

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
507 changes: 507 additions & 0 deletions Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should refine this naming

Large diffs are not rendered by default.

@@ -0,0 +1,60 @@
<?php

namespace Neos\ContentGraph\DoctrineDbalAdapter;

use Doctrine\DBAL\Connection;
use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository;
use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory;
use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryInterface;
use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface;
use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter;
use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;

/**
* Factory for the ContentGraphAdapter implementation for doctrine DBAL
*
* @internal
*/
class ContentGraphAdapterFactory implements ContentGraphAdapterFactoryInterface
{
private NodeFactory $nodeFactory;

private string $tableNamePrefix;

public function __construct(
private readonly Connection $dbalConnection,
private readonly ContentRepositoryId $contentRepositoryId,
NodeTypeManager $nodeTypeManager,
PropertyConverter $propertyConverter
) {
$this->tableNamePrefix = DoctrineDbalContentGraphProjectionFactory::graphProjectionTableNamePrefix(
$contentRepositoryId
);

$dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbalConnection, $this->tableNamePrefix);
$this->nodeFactory = new NodeFactory(
$contentRepositoryId,
$nodeTypeManager,
$propertyConverter,
$dimensionSpacePointsRepository
);
}

public function create(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphAdapterInterface
{
return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $workspaceName, $contentStreamId);
}

public function createFromContentStreamId(ContentStreamId $contentStreamId): ContentGraphAdapterInterface
{
return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, null, $contentStreamId);
}

public function createFromWorkspaceName(WorkspaceName $workspaceName): ContentGraphAdapterInterface
{
return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $workspaceName, null);
}
}
@@ -0,0 +1,26 @@
<?php

namespace Neos\ContentGraph\DoctrineDbalAdapter;

use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryBuilderInterface;
use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryInterface;
use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface;
use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter;
use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;

/**
* Builder to combine injected dependencies and ProjectionFActoryDependencies into a ContentGraphAdapterFactory
* @internal
*/
class ContentGraphAdapterFactoryBuilder implements ContentGraphAdapterFactoryBuilderInterface
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't mind getting around having this, and am happy for suggestions, I don't really see it though. An ugly option would be to to have something along the lines of:

ContentGraphAdapterFactory::injectProjectionDependencies(ProjectionFactoryDependencies $projectionFactoryDependencies)

and the ContentGraphAdapterFactory::__construct just gets the adapter dependencies injected (eg. DBALClient), but then all methods in the ContentGraphAdapterFactory need to throw a "TooEarly" Exception if the projection dependencies were not injected yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIS we only need it to pass in ContentRepositoryId, NodeTypeManager and PropertyConverter – Maybe we can make these explicit dependencies somehow

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

..or even make the NodeFactory an explicit dependency (via some NodeFactoryInterface) but I don't know... Let's discuss

{
public function __construct(private readonly DbalClientInterface $dbalClient)
{
}

public function build(ContentRepositoryId $contentRepositoryId, NodeTypeManager $nodeTypeManager, PropertyConverter $propertyConverter): ContentGraphAdapterFactoryInterface
{
return new ContentGraphAdapterFactory($this->dbalClient->getConnection(), $contentRepositoryId, $nodeTypeManager, $propertyConverter);
}
}
@@ -0,0 +1,43 @@
<?php
namespace Neos\ContentGraph\DoctrineDbalAdapter;

/**
* Encapsulates table name generation for content graph tables
* @internal
*/
final class ContentGraphTableNames
{
private function __construct(private readonly string $tableNamePrefix)
{
}

public static function withPrefix(string $tableNamePrefix): self
{
return new self($tableNamePrefix);
}

public function node(): string
{
return $this->tableNamePrefix . '_node';
}

public function hierachyRelation(): string
{
return $this->tableNamePrefix . '_hierarchyrelation';
}

public function dimensionSpacePoints(): string
{
return $this->tableNamePrefix . '_dimensionspacepoints';
}

public function referenceRelation(): string
{
return $this->tableNamePrefix . '_referencerelation';
}

public function checkpoint(): string
{
return $this->tableNamePrefix . '_checkpoint';
}
}
Expand Up @@ -85,6 +85,8 @@ final class DoctrineDbalContentGraphProjection implements ProjectionInterface, W

private DbalCheckpointStorage $checkpointStorage;

private ContentGraphTableNames $contentGraphTableNames;

public function __construct(
private readonly DbalClientInterface $dbalClient,
private readonly NodeFactory $nodeFactory,
Expand All @@ -94,9 +96,10 @@ public function __construct(
private readonly string $tableNamePrefix,
private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository
) {
$this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix);
$this->checkpointStorage = new DbalCheckpointStorage(
$this->dbalClient->getConnection(),
$this->tableNamePrefix . '_checkpoint',
$this->contentGraphTableNames->checkpoint(),
self::class
);
}
Expand Down Expand Up @@ -174,10 +177,10 @@ public function reset(): void
private function truncateDatabaseTables(): void
{
$connection = $this->dbalClient->getConnection();
$connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_node');
$connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_hierarchyrelation');
$connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_referencerelation');
$connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_dimensionspacepoints');
$connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->node());
$connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->hierachyRelation());
$connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->referenceRelation());
$connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->dimensionSpacePoints());
}

public function canHandle(EventInterface $event): bool
Expand Down Expand Up @@ -311,7 +314,7 @@ private function whenRootNodeAggregateDimensionsWereUpdated(RootNodeAggregateDim
$this->transactional(function () use ($rootNodeAnchorPoint, $event) {
// delete all hierarchy edges of the root node
$this->getDatabaseConnection()->executeUpdate('
DELETE FROM ' . $this->tableNamePrefix . '_hierarchyrelation
DELETE FROM ' . $this->contentGraphTableNames->hierachyRelation() . '
WHERE
parentnodeanchor = :parentNodeAnchor
AND childnodeanchor = :childNodeAnchor
Expand Down Expand Up @@ -360,8 +363,8 @@ private function whenNodeAggregateNameWasChanged(NodeAggregateNameWasChanged $ev
{
$this->transactional(function () use ($event, $eventEnvelope) {
$this->getDatabaseConnection()->executeStatement('
UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h
INNER JOIN ' . $this->tableNamePrefix . '_node n on
UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h
INNER JOIN ' . $this->contentGraphTableNames->node() . ' n on
h.childnodeanchor = n.relationanchorpoint
SET
h.name = :newName,
Expand Down Expand Up @@ -597,7 +600,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void
// 1) Copy HIERARCHY RELATIONS (this is the MAIN OPERATION here)
//
$this->getDatabaseConnection()->executeUpdate('
INSERT INTO ' . $this->tableNamePrefix . '_hierarchyrelation (
INSERT INTO ' . $this->contentGraphTableNames->hierachyRelation() . ' (
parentnodeanchor,
childnodeanchor,
`name`,
Expand All @@ -615,7 +618,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void
h.subtreetags,
"' . $event->newContentStreamId->value . '" AS contentstreamid
FROM
' . $this->tableNamePrefix . '_hierarchyrelation h
' . $this->contentGraphTableNames->hierachyRelation() . ' h
WHERE h.contentstreamid = :sourceContentStreamId
', [
'sourceContentStreamId' => $event->sourceContentStreamId->value
Expand All @@ -632,7 +635,7 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo

// Drop hierarchy relations
$this->getDatabaseConnection()->executeUpdate('
DELETE FROM ' . $this->tableNamePrefix . '_hierarchyrelation
DELETE FROM ' . $this->contentGraphTableNames->hierachyRelation() . '
WHERE
contentstreamid = :contentStreamId
', [
Expand All @@ -641,23 +644,23 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo

// Drop non-referenced nodes (which do not have a hierarchy relation anymore)
$this->getDatabaseConnection()->executeUpdate('
DELETE FROM ' . $this->tableNamePrefix . '_node
DELETE FROM ' . $this->contentGraphTableNames->node() . '
WHERE NOT EXISTS
(
SELECT 1 FROM ' . $this->tableNamePrefix . '_hierarchyrelation
WHERE ' . $this->tableNamePrefix . '_hierarchyrelation.childnodeanchor
= ' . $this->tableNamePrefix . '_node.relationanchorpoint
SELECT 1 FROM ' . $this->contentGraphTableNames->hierachyRelation() . '
WHERE ' . $this->contentGraphTableNames->hierachyRelation() . '.childnodeanchor
= ' . $this->contentGraphTableNames->node() . '.relationanchorpoint
)
');

// Drop non-referenced reference relations (i.e. because the referenced nodes are gone by now)
$this->getDatabaseConnection()->executeUpdate('
DELETE FROM ' . $this->tableNamePrefix . '_referencerelation
DELETE FROM ' . $this->contentGraphTableNames->referenceRelation() . '
WHERE NOT EXISTS
(
SELECT 1 FROM ' . $this->tableNamePrefix . '_node
WHERE ' . $this->tableNamePrefix . '_node.relationanchorpoint
= ' . $this->tableNamePrefix . '_referencerelation.nodeanchorpoint
SELECT 1 FROM ' . $this->contentGraphTableNames->node() . '
WHERE ' . $this->contentGraphTableNames->node() . '.relationanchorpoint
= ' . $this->contentGraphTableNames->referenceRelation() . '.nodeanchorpoint
)
');
});
Expand Down Expand Up @@ -742,7 +745,7 @@ function (NodeRecord $node) use ($eventEnvelope) {
);

// remove old
$this->getDatabaseConnection()->delete($this->tableNamePrefix . '_referencerelation', [
$this->getDatabaseConnection()->delete($this->contentGraphTableNames->referenceRelation(), [
'nodeanchorpoint' => $nodeAnchorPoint?->value,
'name' => $event->referenceName->value
]);
Expand All @@ -751,7 +754,7 @@ function (NodeRecord $node) use ($eventEnvelope) {
$position = 0;
/** @var SerializedNodeReference $reference */
foreach ($event->references as $reference) {
$this->getDatabaseConnection()->insert($this->tableNamePrefix . '_referencerelation', [
$this->getDatabaseConnection()->insert($this->contentGraphTableNames->referenceRelation(), [
'name' => $event->referenceName->value,
'position' => $position,
'nodeanchorpoint' => $nodeAnchorPoint?->value,
Expand Down Expand Up @@ -870,7 +873,7 @@ private function updateNodeRecordWithCopyOnWrite(
// IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges.
$this->getDatabaseConnection()->executeStatement(
'
UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h
UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h
SET
-- if our (copied) node is the child, we update h.childNodeAnchor
h.childnodeanchor
Expand Down Expand Up @@ -914,7 +917,7 @@ private function copyReferenceRelations(
NodeRelationAnchorPoint $destinationRelationAnchorPoint
): void {
$this->getDatabaseConnection()->executeStatement('
INSERT INTO ' . $this->tableNamePrefix . '_referencerelation (
INSERT INTO ' . $this->contentGraphTableNames->referenceRelation() . ' (
nodeanchorpoint,
name,
position,
Expand All @@ -926,7 +929,7 @@ private function copyReferenceRelations(
ref.position,
ref.destinationnodeaggregateid
FROM
' . $this->tableNamePrefix . '_referencerelation ref
' . $this->contentGraphTableNames->referenceRelation() . ' ref
WHERE ref.nodeanchorpoint = :sourceNodeAnchorPoint
', [
'sourceNodeAnchorPoint' => $sourceRelationAnchorPoint->value,
Expand All @@ -945,8 +948,8 @@ private function whenDimensionSpacePointWasMoved(DimensionSpacePointWasMoved $ev
// 1) originDimensionSpacePoint on Node
$rel = $this->getDatabaseConnection()->executeQuery(
'SELECT n.relationanchorpoint, n.origindimensionspacepointhash
FROM ' . $this->tableNamePrefix . '_node n
INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h
FROM ' . $this->contentGraphTableNames->node() . ' n
INNER JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' h
ON h.childnodeanchor = n.relationanchorpoint

AND h.contentstreamid = :contentStreamId
Expand Down Expand Up @@ -975,7 +978,7 @@ function (NodeRecord $nodeRecord) use ($event) {
// 2) hierarchy relations
$this->getDatabaseConnection()->executeStatement(
'
UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h
UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h
SET
h.dimensionspacepointhash = :newDimensionSpacePointHash
WHERE
Expand All @@ -999,7 +1002,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded
// 1) hierarchy relations
$this->getDatabaseConnection()->executeStatement(
'
INSERT INTO ' . $this->tableNamePrefix . '_hierarchyrelation (
INSERT INTO ' . $this->contentGraphTableNames->hierachyRelation() . ' (
parentnodeanchor,
childnodeanchor,
`name`,
Expand All @@ -1017,7 +1020,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded
:newDimensionSpacePointHash AS dimensionspacepointhash,
h.contentstreamid
FROM
' . $this->tableNamePrefix . '_hierarchyrelation h
' . $this->contentGraphTableNames->hierachyRelation() . ' h
WHERE h.contentstreamid = :contentStreamId
AND h.dimensionspacepointhash = :sourceDimensionSpacePointHash',
[
Expand Down
Expand Up @@ -17,9 +17,12 @@ class DoctrineDbalContentGraphSchemaBuilder
{
private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci';

private readonly ContentGraphTableNames $contentGraphTableNames;

public function __construct(
private readonly string $tableNamePrefix
string $tableNamePrefix
) {
$this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix);
}

public function buildSchema(AbstractSchemaManager $schemaManager): Schema
Expand All @@ -34,7 +37,7 @@ public function buildSchema(AbstractSchemaManager $schemaManager): Schema

private function createNodeTable(): Table
{
$table = new Table($this->tableNamePrefix . '_node', [
$table = new Table($this->contentGraphTableNames->node(), [
DbalSchemaFactory::columnForNodeAnchorPoint('relationanchorpoint')->setAutoincrement(true),
DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotnull(false),
DbalSchemaFactory::columnForDimensionSpacePointHash('origindimensionspacepointhash')->setNotnull(false),
Expand All @@ -55,7 +58,7 @@ private function createNodeTable(): Table

private function createHierarchyRelationTable(): Table
{
$table = new Table($this->tableNamePrefix . '_hierarchyrelation', [
$table = new Table($this->contentGraphTableNames->hierachyRelation(), [
(new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'),
(new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true),
DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotnull(true),
Expand All @@ -72,7 +75,7 @@ private function createHierarchyRelationTable(): Table

private function createDimensionSpacePointsTable(): Table
{
$table = new Table($this->tableNamePrefix . '_dimensionspacepoints', [
$table = new Table($this->contentGraphTableNames->dimensionSpacePoints(), [
DbalSchemaFactory::columnForDimensionSpacePointHash('hash')->setNotnull(true),
DbalSchemaFactory::columnForDimensionSpacePoint('dimensionspacepoint')->setNotnull(true)
]);
Expand All @@ -83,7 +86,7 @@ private function createDimensionSpacePointsTable(): Table

private function createReferenceRelationTable(): Table
{
$table = new Table($this->tableNamePrefix . '_referencerelation', [
$table = new Table($this->contentGraphTableNames->referenceRelation(), [
(new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'),
(new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true),
DbalSchemaFactory::columnForNodeAnchorPoint('nodeanchorpoint'),
Expand Down
Expand Up @@ -15,7 +15,6 @@
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\DimensionSpacePointsRepository;
use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues;
Expand Down Expand Up @@ -53,6 +52,7 @@ public function __construct(
*/
public function updateToDatabase(Connection $databaseConnection, string $tableNamePrefix): void
{

$databaseConnection->update(
$tableNamePrefix . '_node',
[
Expand Down