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

Save skipped an d errored migrations info #1193

Draft
wants to merge 1 commit into
base: 3.3.x
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions lib/Doctrine/Migrations/Exception/MetadataStorageError.php
Expand Up @@ -8,6 +8,11 @@

final class MetadataStorageError extends RuntimeException implements MigrationException
{
public static function errorSavingMetadata(): self
{
return new self('The metadata storage is not up to date, please run the sync-metadata-storage command to fix this issue.');
}

public static function notUpToDate(): self
{
return new self('The metadata storage is not up to date, please run the sync-metadata-storage command to fix this issue.');
Expand Down
Expand Up @@ -37,6 +37,10 @@

final class TableMetadataStorage implements MetadataStorage
{
private const RUN_EXECUTED = 'execute';
private const RUN_SKIPPED = 'skip';
private const RUN_ERRORED = 'error';

/** @var bool */
private $isInitialized;

Expand Down Expand Up @@ -87,7 +91,15 @@ public function getExecutedMigrations(): ExecutedMigrationsList
}

$this->checkInitialization();
$rows = $this->connection->fetchAllAssociative(sprintf('SELECT * FROM %s', $this->configuration->getTableName()));
$rows = $this->connection->fetchAllAssociative(
sprintf(
'SELECT * FROM %s WHERE %s = :reason',
$this->configuration->getTableName(),
$this->configuration->getExecutionReasonColumnName()
),
['reason' => self::RUN_EXECUTED],
['reason' => Types::STRING]
);

$migrations = [];
foreach ($rows as $row) {
Expand Down Expand Up @@ -136,6 +148,10 @@ public function complete(ExecutionResult $result): void
{
$this->checkInitialization();

$reason = ($result->isSkipped()
? 'skipp'
: ($result->hasError() ? 'error' : 'execute'));

if ($result->getDirection() === Direction::DOWN) {
$this->connection->delete($this->configuration->getTableName(), [
$this->configuration->getVersionColumnName() => (string) $result->getVersion(),
Expand All @@ -145,10 +161,12 @@ public function complete(ExecutionResult $result): void
$this->configuration->getVersionColumnName() => (string) $result->getVersion(),
$this->configuration->getExecutedAtColumnName() => $result->getExecutedAt(),
$this->configuration->getExecutionTimeColumnName() => $result->getTime() === null ? null : (int) round($result->getTime() * 1000),
$this->configuration->getExecutionReasonColumnName() => $reason,
], [
Types::STRING,
Types::DATETIME_MUTABLE,
Types::INTEGER,
Types::STRING,
]);
}
}
Expand Down Expand Up @@ -228,6 +246,11 @@ private function getExpectedTable(): Table
);
$schemaChangelog->addColumn($this->configuration->getExecutedAtColumnName(), 'datetime', ['notnull' => false]);
$schemaChangelog->addColumn($this->configuration->getExecutionTimeColumnName(), 'integer', ['notnull' => false]);
$schemaChangelog->addColumn(
$this->configuration->getExecutionReasonColumnName(),
'string',
['notnull' => true, 'default' => self::RUN_EXECUTED]
);

$schemaChangelog->setPrimaryKey([$this->configuration->getVersionColumnName()]);

Expand Down
Expand Up @@ -21,6 +21,9 @@ final class TableMetadataStorageConfiguration implements MetadataStorageConfigur
/** @var string */
private $executionTimeColumnName = 'execution_time';

/** @var string */
private $executionReasonColumnName = 'reason';

public function getTableName(): string
{
return $this->tableName;
Expand Down Expand Up @@ -66,6 +69,16 @@ public function getExecutionTimeColumnName(): string
return $this->executionTimeColumnName;
}

public function getExecutionReasonColumnName(): string
{
return $this->executionReasonColumnName;
}

public function setExecutionReasonColumnName(string $executionReasonColumnName): void
{
$this->executionReasonColumnName = $executionReasonColumnName;
}

public function setExecutionTimeColumnName(string $executionTimeColumnName): void
{
$this->executionTimeColumnName = $executionTimeColumnName;
Expand Down
16 changes: 15 additions & 1 deletion lib/Doctrine/Migrations/Version/DbalExecutor.php
Expand Up @@ -10,6 +10,7 @@
use Doctrine\Migrations\AbstractMigration;
use Doctrine\Migrations\EventDispatcher;
use Doctrine\Migrations\Events;
use Doctrine\Migrations\Exception\MetadataStorageError;
use Doctrine\Migrations\Exception\SkipMigration;
use Doctrine\Migrations\Metadata\MigrationPlan;
use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
Expand Down Expand Up @@ -105,6 +106,10 @@ public function execute(
);

$result->setSql($this->sql);
} catch (MetadataStorageError $e) {
$result->setError(true, $e);

$this->migrationEnd($e, $plan, $result, $configuration);
} catch (SkipMigration $e) {
$result->setSkipped(true);

Expand Down Expand Up @@ -209,7 +214,11 @@ private function executeMigration(
$this->logger->info('Migration {version} {direction} (took {time}ms, used {memory} memory)', $params);

if (! $configuration->isDryRun()) {
$this->metadataStorage->complete($result);
try {
$this->metadataStorage->complete($result);
} catch (Throwable $e) {
throw MetadataStorageError::errorSavingMetadata();
}
}

if ($migration->isTransactional()) {
Expand Down Expand Up @@ -251,7 +260,12 @@ private function getMigrationHeader(MigrationPlan $planItem, AbstractMigration $

private function migrationEnd(Throwable $e, MigrationPlan $plan, ExecutionResult $result, MigratorConfiguration $configuration): void
{
if (! $configuration->isDryRun() && ! ($e instanceof MetadataStorageError)) {
$this->metadataStorage->complete($result);
}

$migration = $plan->getMigration();

if ($migration->isTransactional()) {
//only rollback transaction if in transactional mode
TransactionHelper::rollbackIfInTransaction($this->connection);
Expand Down
Expand Up @@ -118,11 +118,13 @@ public function testMigratedVersionUpdate(): void
'version' => '1234',
'executed_at' => null,
'execution_time' => null,
'reason' => 'execute',
], $rows[0]);
self::assertSame([
'version' => 'Foo\\5678',
'executed_at' => null,
'execution_time' => null,
'reason' => 'execute',
], $rows[1]);
}
}
Expand Up @@ -183,6 +183,7 @@ public function testComplete(): void
'version' => '1230',
'executed_at' => '2010-01-05 10:30:21',
'execution_time' => '31000',
'reason' => 'execute',
],
], $rows);
}
Expand All @@ -206,6 +207,7 @@ public function testCompleteWithFloatTime(): void
'version' => '1230',
'executed_at' => '2010-01-05 10:30:21',
'execution_time' => '31490',
'reason' => 'execute',
],
], $rows);
}
Expand All @@ -232,11 +234,13 @@ public function testCompleteWillAlwaysCastTimeToInteger(): void
$config->getVersionColumnName() => '1230',
$config->getExecutedAtColumnName() => $executedAt,
$config->getExecutionTimeColumnName() => 31000,
$config->getExecutionReasonColumnName() => 'execute',
], $params);
self::assertSame([
Types::STRING,
Types::DATETIME_MUTABLE,
Types::INTEGER,
Types::STRING,
], $types);

return 1;
Expand Down
63 changes: 36 additions & 27 deletions tests/Doctrine/Migrations/Tests/Version/ExecutorTest.php
Expand Up @@ -106,6 +106,8 @@ public function testExecuteUp(): void
->expects(self::once())
->method('complete')->willReturnCallback(static function (ExecutionResult $result): void {
self::assertSame(Direction::UP, $result->getDirection());
self::assertFalse($result->isSkipped());
self::assertFalse($result->hasError());
self::assertNotNull($result->getTime());
self::assertNotNull($result->getExecutedAt());
});
Expand Down Expand Up @@ -169,6 +171,8 @@ public function testExecuteDown(): void
->expects(self::once())
->method('complete')->willReturnCallback(static function (ExecutionResult $result): void {
self::assertSame(Direction::DOWN, $result->getDirection());
self::assertFalse($result->isSkipped());
self::assertFalse($result->hasError());
self::assertNotNull($result->getTime());
self::assertNotNull($result->getExecutedAt());
});
Expand Down Expand Up @@ -266,8 +270,14 @@ public function testExecuteDryRun(): void
public function testSkipMigration(): void
{
$this->metadataStorage
->expects(self::never())
->method('complete');
->expects(self::once())
->method('complete')->willReturnCallback(static function (ExecutionResult $result): void {
self::assertSame(Direction::UP, $result->getDirection());
self::assertTrue($result->isSkipped());
self::assertFalse($result->hasError());
self::assertNull($result->getTime());
self::assertNotNull($result->getExecutedAt());
});

$migratorConfiguration = (new MigratorConfiguration())
->setTimeAllQueries(true);
Expand Down Expand Up @@ -363,8 +373,14 @@ public function onMigrationsVersionExecuted(): void
public function testErrorMigration(): void
{
$this->metadataStorage
->expects(self::never())
->method('complete');
->expects(self::once())
->method('complete')->willReturnCallback(static function (ExecutionResult $result): void {
self::assertSame(Direction::UP, $result->getDirection());
self::assertFalse($result->isSkipped());
self::assertTrue($result->hasError());
self::assertNull($result->getTime());
self::assertNotNull($result->getExecutedAt());
});

$migratorConfiguration = (new MigratorConfiguration())
->setTimeAllQueries(true);
Expand Down Expand Up @@ -472,30 +488,23 @@ public function onMigrationsVersionSkipped(): void
$this->eventManager->addEventListener(Events::onMigrationsVersionExecuted, $listener);
$this->eventManager->addEventListener(Events::onMigrationsVersionSkipped, $listener);

$migrationSucceed = false;
try {
$this->versionExecutor->execute(
$plan,
$migratorConfiguration
);
$migrationSucceed = true;
} catch (Throwable $e) {
self::assertFalse($listener->onMigrationsVersionExecuted);
self::assertTrue($listener->onMigrationsVersionSkipped);
self::assertTrue($listener->onMigrationsVersionExecuting);

$result = $plan->getResult();
self::assertNotNull($result);
self::assertSame([], $result->getSql());
self::assertSame([], $result->getSql());
self::assertSame(State::POST, $result->getState());
self::assertTrue($this->migration->preUpExecuted);
self::assertTrue($this->migration->postUpExecuted);
self::assertFalse($this->migration->preDownExecuted);
self::assertFalse($this->migration->postDownExecuted);
}
$this->versionExecutor->execute(
$plan,
$migratorConfiguration
);
self::assertFalse($listener->onMigrationsVersionExecuted);
self::assertTrue($listener->onMigrationsVersionSkipped);
self::assertTrue($listener->onMigrationsVersionExecuting);

self::assertFalse($migrationSucceed);
$result = $plan->getResult();
self::assertNotNull($result);
self::assertSame([], $result->getSql());
self::assertSame([], $result->getSql());
self::assertSame(State::POST, $result->getState());
self::assertTrue($this->migration->preUpExecuted);
self::assertTrue($this->migration->postUpExecuted);
self::assertFalse($this->migration->preDownExecuted);
self::assertFalse($this->migration->postDownExecuted);
}

/**
Expand Down