From 5e1ccaf0d7a8046d8fe55216199c05542d144c7a Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Fri, 12 Jan 2024 17:13:16 +0100 Subject: [PATCH] TASK: Adjust to inlined ProvidesSetupInterface and ProvidesStatusInterface Adjusts classes `DoctrineEventStore` and `DoctrineCheckpointStorage` to the adjusted `neos/eventstore` package and fixes implementation of `DoctrineEventStore::status()` Related: https://github.com/neos/eventstore/issues/16 --- composer.json | 2 +- src/DoctrineCheckpointStorage.php | 10 ++-- src/DoctrineEventStore.php | 48 ++++++++++++-------- tests/Integration/DoctrineEventStoreTest.php | 41 +++++++++++++++++ 4 files changed, 75 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index dedd332..5ffba3e 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ ], "require": { "php": "^8.1", - "neos/eventstore": "1.0.0-beta1", + "neos/eventstore": "@dev", "doctrine/dbal": "^2", "webmozart/assert": "^1.10", "psr/clock": "^1" diff --git a/src/DoctrineCheckpointStorage.php b/src/DoctrineCheckpointStorage.php index 673b81e..6efccd1 100644 --- a/src/DoctrineCheckpointStorage.php +++ b/src/DoctrineCheckpointStorage.php @@ -3,8 +3,8 @@ namespace Neos\EventStore\DoctrineAdapter; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Exception\DriverException as DBALDriverException; use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Exception\DriverException as DBALDriverException; use Doctrine\DBAL\Exception\LockWaitTimeoutException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Platforms\MySqlPlatform; @@ -16,10 +16,8 @@ use Neos\EventStore\CatchUp\CheckpointStorageInterface; use Neos\EventStore\Exception\CheckpointException; use Neos\EventStore\Model\Event\SequenceNumber; -use Neos\EventStore\Model\EventStore\SetupResult; -use Neos\EventStore\ProvidesSetupInterface; -final class DoctrineCheckpointStorage implements CheckpointStorageInterface, ProvidesSetupInterface +final class DoctrineCheckpointStorage implements CheckpointStorageInterface { private MySqlPlatform|PostgreSqlPlatform $platform; @@ -94,7 +92,7 @@ public function getHighestAppliedSequenceNumber(): SequenceNumber return SequenceNumber::fromInteger((int)$highestAppliedSequenceNumber); } - public function setup(): SetupResult + public function setup(): void { $schemaManager = $this->connection->getSchemaManager(); if (!$schemaManager instanceof AbstractSchemaManager) { @@ -115,7 +113,5 @@ public function setup(): SetupResult } catch (UniqueConstraintViolationException $e) { // table and row already exists, ignore } - - return SetupResult::success(''); } } diff --git a/src/DoctrineEventStore.php b/src/DoctrineEventStore.php index 949ad91..8acc583 100644 --- a/src/DoctrineEventStore.php +++ b/src/DoctrineEventStore.php @@ -21,7 +21,6 @@ use Neos\EventStore\Model\Event\Version; use Neos\EventStore\Model\Events; use Neos\EventStore\Model\EventStore\CommitResult; -use Neos\EventStore\Model\EventStore\SetupResult; use Neos\EventStore\Model\EventStore\Status; use Neos\EventStore\Model\EventStream\EventStreamFilter; use Neos\EventStore\Model\EventStream\EventStreamInterface; @@ -29,11 +28,9 @@ use Neos\EventStore\Model\EventStream\MaybeVersion; use Neos\EventStore\Model\EventStream\VirtualStreamName; use Neos\EventStore\Model\EventStream\VirtualStreamType; -use Neos\EventStore\ProvidesSetupInterface; -use Neos\EventStore\ProvidesStatusInterface; use Psr\Clock\ClockInterface; -final class DoctrineEventStore implements EventStoreInterface, ProvidesSetupInterface, ProvidesStatusInterface +final class DoctrineEventStore implements EventStoreInterface { private readonly ClockInterface $clock; @@ -127,27 +124,42 @@ public function deleteStream(StreamName $streamName): void public function status(): Status { - return Status::error('not implemented'); + try { + $this->connection->connect(); + } catch (DbalException $e) { + return Status::error(sprintf('Failed to connect to database: %s', $e->getMessage())); + } + $requiredSqlStatements = $this->determineRequiredSqlStatements(); + if ($requiredSqlStatements !== []) { + return Status::setupRequired(sprintf('The following SQL statement%s required: %s', count($requiredSqlStatements) !== 1 ? 's are' : ' is', implode(chr(10), $requiredSqlStatements))); + } + return Status::ok(); } - public function setup(): SetupResult + public function setup(): void { - $schemaManager = $this->connection->getSchemaManager(); - assert($schemaManager !== null); - $fromSchema = $schemaManager->createSchema(); - $schemaDiff = (new Comparator())->compare($fromSchema, $this->createEventStoreSchema()); - - $statements = $schemaDiff->toSaveSql($this->connection->getDatabasePlatform()); - if ($statements === []) { - return SetupResult::success('Table schema is up to date, no migration required'); - } - - foreach ($statements as $statement) { + foreach ($this->determineRequiredSqlStatements() as $statement) { $this->connection->executeStatement($statement); } - return SetupResult::success('Event store table created/updated successfully'); } + /** + * @return array + */ + private function determineRequiredSqlStatements(): array + { + $schemaManager = $this->connection->getSchemaManager(); + assert($schemaManager !== null); + $platform = $this->connection->getDatabasePlatform(); + assert($platform !== null); + if (!$schemaManager->tablesExist($this->eventTableName)) { + return $platform->getCreateTableSQL($this->createEventStoreSchema()->getTable($this->eventTableName)); + } + $tableSchema = $schemaManager->listTableDetails($this->eventTableName); + $fromSchema = new Schema([$tableSchema], [], $schemaManager->createSchemaConfig()); + $schemaDiff = (new Comparator())->compare($fromSchema, $this->createEventStoreSchema()); + return $schemaDiff->toSaveSql($platform); + } // ---------------------------------- diff --git a/tests/Integration/DoctrineEventStoreTest.php b/tests/Integration/DoctrineEventStoreTest.php index 54c8575..f057aa0 100644 --- a/tests/Integration/DoctrineEventStoreTest.php +++ b/tests/Integration/DoctrineEventStoreTest.php @@ -4,10 +4,12 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Exception as DbalException; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Neos\EventStore\DoctrineAdapter\DoctrineEventStore; use Neos\EventStore\EventStoreInterface; +use Neos\EventStore\Model\EventStore\StatusType; use Neos\EventStore\Tests\Integration\AbstractEventStoreTestBase; use PHPUnit\Framework\Attributes\CoversClass; @@ -50,4 +52,43 @@ public static function eventTableName(): string return 'events_test'; } + public function test_setup_throws_exception_if_database_connection_fails(): void + { + $connection = DriverManager::getConnection(['url' => 'mysql://invalid-connection']); + $eventStore = new DoctrineEventStore($connection, self::eventTableName()); + + $this->expectException(DbalException::class); + $eventStore->setup(); + } + + public function test_status_returns_error_status_if_database_connection_fails(): void + { + $connection = DriverManager::getConnection(['url' => 'mysql://invalid-connection']); + $eventStore = new DoctrineEventStore($connection, self::eventTableName()); + self::assertSame($eventStore->status()->type, StatusType::ERROR); + } + + public function test_status_returns_setup_required_status_if_event_table_is_missing(): void + { + $connection = DriverManager::getConnection(['url' => 'sqlite:///:memory:']); + $eventStore = new DoctrineEventStore($connection, self::eventTableName()); + self::assertSame($eventStore->status()->type, StatusType::SETUP_REQUIRED); + } + + public function test_status_returns_setup_required_status_if_event_table_requires_update(): void + { + $connection = self::connection(); + $eventStore = new DoctrineEventStore($connection, self::eventTableName()); + $eventStore->setup(); + $connection->executeStatement('ALTER TABLE ' . $connection->quote(self::eventTableName()) . ' RENAME COLUMN metadata TO metadata_renamed'); + self::assertSame($eventStore->status()->type, StatusType::SETUP_REQUIRED); + } + + public function test_status_returns_ok_status_if_event_table_is_up_to_date(): void + { + $connection = self::connection(); + $eventStore = new DoctrineEventStore($connection, self::eventTableName()); + $eventStore->setup(); + self::assertSame($eventStore->status()->type, StatusType::OK); + } }