diff --git a/Makefile b/Makefile index 01f437b8..4f9b56d6 100644 --- a/Makefile +++ b/Makefile @@ -41,11 +41,11 @@ phpunit-integration: vendor .PHONY: phpunit-integration-postgres phpunit-integration-postgres: vendor ## run phpunit integration tests on postgres - DB_URL="pdo-pgsql://postgres:postgres@localhost:5432/eventstore?charset=utf8" vendor/bin/phpunit --testsuite=integration + DB_URL="pdo-pgsql://postgres:postgres@127.0.0.1:5432/eventstore?charset=utf8" vendor/bin/phpunit --testsuite=integration .PHONY: phpunit-integration-mysql phpunit-integration-mysql: vendor ## run phpunit integration tests on mysql - DB_URL="pdo-mysql://root@localhost:3306/eventstore?charset=utf8" vendor/bin/phpunit --testsuite=integration + DB_URL="pdo-mysql://root@127.0.0.1:3306/eventstore?charset=utf8" vendor/bin/phpunit --testsuite=integration .PHONY: phpunit-unit phpunit-unit: vendor ## run phpunit unit tests diff --git a/baseline.xml b/baseline.xml index 7504e13d..2e51abc6 100644 --- a/baseline.xml +++ b/baseline.xml @@ -95,6 +95,12 @@ + + + + + + diff --git a/src/Cryptography/DoctrineCipherKeyStore.php b/src/Cryptography/DoctrineCipherKeyStore.php index 2bfb00da..fdd4c850 100644 --- a/src/Cryptography/DoctrineCipherKeyStore.php +++ b/src/Cryptography/DoctrineCipherKeyStore.php @@ -6,6 +6,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Schema; +use Patchlevel\EventSourcing\Schema\DoctrineHelper; use Patchlevel\EventSourcing\Schema\DoctrineSchemaConfigurator; use Patchlevel\Hydrator\Cryptography\Cipher\CipherKey; use Patchlevel\Hydrator\Cryptography\Store\CipherKeyNotExists; @@ -80,7 +81,7 @@ public function remove(string $id): void public function configureSchema(Schema $schema, Connection $connection): void { - if ($connection !== $this->connection) { + if (!DoctrineHelper::sameDatabase($this->connection, $connection)) { return; } diff --git a/src/Schema/DoctrineHelper.php b/src/Schema/DoctrineHelper.php new file mode 100644 index 00000000..889c9cf6 --- /dev/null +++ b/src/Schema/DoctrineHelper.php @@ -0,0 +1,44 @@ +getParams() === $connectionB->getParams()) { + return true; + } + + $checkTable = 'same_db_check_' . bin2hex(random_bytes(7)); + $connectionA->executeStatement(sprintf('CREATE TABLE %s (id INTEGER NOT NULL)', $checkTable)); + + try { + $connectionB->executeStatement(sprintf('DROP TABLE %s', $checkTable)); + } catch (Throwable) { + // ignore + } + + try { + $connectionA->executeStatement(sprintf('DROP TABLE %s', $checkTable)); + + return false; + } catch (TableNotFoundException) { + return true; + } + } +} diff --git a/src/Store/DoctrineDbalStore.php b/src/Store/DoctrineDbalStore.php index 76c775bd..902d6c41 100644 --- a/src/Store/DoctrineDbalStore.php +++ b/src/Store/DoctrineDbalStore.php @@ -21,6 +21,7 @@ use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Message\Serializer\DefaultHeadersSerializer; use Patchlevel\EventSourcing\Message\Serializer\HeadersSerializer; +use Patchlevel\EventSourcing\Schema\DoctrineHelper; use Patchlevel\EventSourcing\Schema\DoctrineSchemaConfigurator; use Patchlevel\EventSourcing\Serializer\EventSerializer; use Patchlevel\EventSourcing\Store\Criteria\AggregateIdCriterion; @@ -314,7 +315,7 @@ public function transactional(Closure $function): void public function configureSchema(Schema $schema, Connection $connection): void { - if ($this->connection !== $connection) { + if (!DoctrineHelper::sameDatabase($this->connection, $connection)) { return; } diff --git a/src/Store/StreamDoctrineDbalStore.php b/src/Store/StreamDoctrineDbalStore.php index 69ef4b9e..83a24a24 100644 --- a/src/Store/StreamDoctrineDbalStore.php +++ b/src/Store/StreamDoctrineDbalStore.php @@ -21,6 +21,7 @@ use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Message\Serializer\DefaultHeadersSerializer; use Patchlevel\EventSourcing\Message\Serializer\HeadersSerializer; +use Patchlevel\EventSourcing\Schema\DoctrineHelper; use Patchlevel\EventSourcing\Schema\DoctrineSchemaConfigurator; use Patchlevel\EventSourcing\Serializer\EventSerializer; use Patchlevel\EventSourcing\Store\Criteria\ArchivedCriterion; @@ -396,7 +397,7 @@ public function archive(Criteria|null $criteria = null): void public function configureSchema(Schema $schema, Connection $connection): void { - if ($this->connection !== $connection) { + if (!DoctrineHelper::sameDatabase($this->connection, $connection)) { return; } diff --git a/src/Subscription/Store/DoctrineSubscriptionStore.php b/src/Subscription/Store/DoctrineSubscriptionStore.php index 08cde34d..424f1cce 100644 --- a/src/Subscription/Store/DoctrineSubscriptionStore.php +++ b/src/Subscription/Store/DoctrineSubscriptionStore.php @@ -15,6 +15,7 @@ use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Patchlevel\EventSourcing\Clock\SystemClock; +use Patchlevel\EventSourcing\Schema\DoctrineHelper; use Patchlevel\EventSourcing\Schema\DoctrineSchemaConfigurator; use Patchlevel\EventSourcing\Subscription\RunMode; use Patchlevel\EventSourcing\Subscription\Status; @@ -208,6 +209,10 @@ public function inLock(Closure $closure): mixed public function configureSchema(Schema $schema, Connection $connection): void { + if (!DoctrineHelper::sameDatabase($this->connection, $connection)) { + return; + } + $table = $schema->createTable($this->tableName); $table->addColumn('id', Types::STRING) diff --git a/tests/DbalManager.php b/tests/DbalManager.php index c4a39af7..240e5c21 100644 --- a/tests/DbalManager.php +++ b/tests/DbalManager.php @@ -7,6 +7,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\AbstractSQLiteDriver; use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Tools\DsnParser; use Patchlevel\EventSourcing\Console\DoctrineHelper; use RuntimeException; @@ -45,6 +46,14 @@ public static function createConnection(string $dbName = self::DEFAULT_DB_NAME): $databases = $schemaManager->listDatabases(); if (in_array($dbName, $databases, true)) { + if ($tempConnection->getDatabasePlatform() instanceof PostgreSQLPlatform) { + $tempConnection->executeStatement(" + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = '{$dbName}'; + "); + } + $schemaManager->dropDatabase($dbName); } diff --git a/tests/Integration/Store/DoctrineDbalStoreTest.php b/tests/Integration/Store/DoctrineDbalStoreTest.php index 3eee96ad..04511fc7 100644 --- a/tests/Integration/Store/DoctrineDbalStoreTest.php +++ b/tests/Integration/Store/DoctrineDbalStoreTest.php @@ -6,6 +6,7 @@ use DateTimeImmutable; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Schema; use Patchlevel\EventSourcing\Aggregate\AggregateHeader; use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Schema\DoctrineSchemaDirector; @@ -224,4 +225,38 @@ public function testLoad(): void $stream?->close(); } } + + public function testConfigureSchemaSameDatabase(): void + { + $connection = DbalManager::createConnection(); + $otherConnection = DbalManager::createConnection(); + + $store = new DoctrineDbalStore( + $connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + $schema = new Schema(); + + $store->configureSchema($schema, $otherConnection); + + self::assertTrue($schema->hasTable('eventstore')); + } + + public function testConfigureSchemaNotSameDatabase(): void + { + $connection = DbalManager::createConnection(); + $otherConnection = DbalManager::createConnection('other'); + + $store = new DoctrineDbalStore( + $connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + $schema = new Schema(); + + $store->configureSchema($schema, $otherConnection); + + self::assertFalse($schema->hasTable('eventstore')); + } } diff --git a/tests/Integration/Store/StreamDoctrineDbalStoreTest.php b/tests/Integration/Store/StreamDoctrineDbalStoreTest.php index 9d22d7b5..9b83b285 100644 --- a/tests/Integration/Store/StreamDoctrineDbalStoreTest.php +++ b/tests/Integration/Store/StreamDoctrineDbalStoreTest.php @@ -6,6 +6,7 @@ use DateTimeImmutable; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Schema; use Patchlevel\EventSourcing\Clock\FrozenClock; use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Schema\DoctrineSchemaDirector; @@ -538,4 +539,40 @@ public function testRemove(): void self::assertEquals(['foo'], $streams); } + + public function testConfigureSchemaSameDatabase(): void + { + $connection = DbalManager::createConnection(); + $otherConnection = DbalManager::createConnection(); + + $store = new StreamDoctrineDbalStore( + $connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + clock: $this->clock, + ); + + $schema = new Schema(); + + $store->configureSchema($schema, $otherConnection); + + self::assertTrue($schema->hasTable('event_store')); + } + + public function testConfigureSchemaNotSameDatabase(): void + { + $connection = DbalManager::createConnection(); + $otherConnection = DbalManager::createConnection('other'); + + $store = new StreamDoctrineDbalStore( + $connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + clock: $this->clock, + ); + + $schema = new Schema(); + + $store->configureSchema($schema, $otherConnection); + + self::assertFalse($schema->hasTable('event_store')); + } } diff --git a/tests/Unit/Store/DoctrineDbalStoreTest.php b/tests/Unit/Store/DoctrineDbalStoreTest.php index b6807935..0d968ff5 100644 --- a/tests/Unit/Store/DoctrineDbalStoreTest.php +++ b/tests/Unit/Store/DoctrineDbalStoreTest.php @@ -1375,9 +1375,11 @@ public function testWait(): void $doctrineDbalStore->wait(100); } - public function testConfigureSchemaWithDifferentConnections(): void + public function testConfigureSchemaWithDifferentDatabase(): void { $connection = $this->createMock(Connection::class); + $connection->expects($this->once())->method('getParams')->willReturn(['dbname' => 'db']); + $eventSerializer = $this->createMock(EventSerializer::class); $headersSerializer = $this->createMock(HeadersSerializer::class); @@ -1386,8 +1388,12 @@ public function testConfigureSchemaWithDifferentConnections(): void $eventSerializer, $headersSerializer, ); + + $differentConnection = $this->createMock(Connection::class); + $differentConnection->expects($this->once())->method('getParams')->willReturn(['dbname' => 'db2']); + $schema = new Schema(); - $doctrineDbalStore->configureSchema($schema, $this->createMock(Connection::class)); + $doctrineDbalStore->configureSchema($schema, $differentConnection); self::assertEquals(new Schema(), $schema); } diff --git a/tests/Unit/Store/StreamDoctrineDbalStoreTest.php b/tests/Unit/Store/StreamDoctrineDbalStoreTest.php index 1e9d472a..c78414c2 100644 --- a/tests/Unit/Store/StreamDoctrineDbalStoreTest.php +++ b/tests/Unit/Store/StreamDoctrineDbalStoreTest.php @@ -1404,9 +1404,11 @@ public function testWait(): void $doctrineDbalStore->wait(100); } - public function testConfigureSchemaWithDifferentConnections(): void + public function testConfigureSchemaWithDifferentDatabase(): void { $connection = $this->createMock(Connection::class); + $connection->expects($this->once())->method('getParams')->willReturn(['dbname' => 'db']); + $eventSerializer = $this->createMock(EventSerializer::class); $headersSerializer = $this->createMock(HeadersSerializer::class); @@ -1415,8 +1417,12 @@ public function testConfigureSchemaWithDifferentConnections(): void $eventSerializer, $headersSerializer, ); + + $differentConnection = $this->createMock(Connection::class); + $connection->expects($this->once())->method('getParams')->willReturn(['dbname' => 'db2']); + $schema = new Schema(); - $doctrineDbalStore->configureSchema($schema, $this->createMock(Connection::class)); + $doctrineDbalStore->configureSchema($schema, $differentConnection); self::assertEquals(new Schema(), $schema); }