Skip to content

Commit

Permalink
add full-rollback option for production migrations
Browse files Browse the repository at this point in the history
Usecase: running migrations during deploy should not alter the database
at all. It’s handy to only rollback the last migration during
development, so the original behaviour is retained.

Side effect: migrations are committed at once, not per single migration.
Also affects original continue mode, not just full rollback mode.
Original behaviour could have caused transient problems during deploy.
  • Loading branch information
Mikulas committed Jul 17, 2015
1 parent c184cf1 commit e179723
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 11 deletions.
5 changes: 4 additions & 1 deletion src/Bridges/SymfonyConsole/ContinueCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ protected function configure()
$this->setDescription('Updates database schema by running all new migrations');
$this->setHelp("If table 'migrations' does not exist in current database, it is created automatically.");
$this->addOption('production', NULL, InputOption::VALUE_NONE, 'Will not import dummy data');
$this->addOption('full-rollback', 'r', InputOption::VALUE_NONE, 'Upon failing, rollback all migrations, not only the failed on. <comment>Only works reliably with PostgreSQL.</comment>');
}


protected function execute(InputInterface $input, OutputInterface $output)
{
$mode = $input->getOption('full-rollback') ? Runner::MODE_CONTINUE_FULL_ROLLBACK : Runner::MODE_CONTINUE;

$withDummy = !$input->getOption('production');
$this->runMigrations(Runner::MODE_CONTINUE, $withDummy);
$this->runMigrations($mode, $withDummy);
}

}
36 changes: 26 additions & 10 deletions src/Engine/Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Runner
{
/** @const modes */
const MODE_CONTINUE = 'continue';
const MODE_CONTINUE_FULL_ROLLBACK = 'continue-full-rollback';
const MODE_RESET = 'reset';
const MODE_INIT = 'init';

Expand Down Expand Up @@ -80,7 +81,7 @@ public function addExtensionHandler($extension, IExtensionHandler $handler)


/**
* @param string $mode self::MODE_CONTINUE|self::MODE_RESET|self::MODE_INIT
* @param string $mode self::MODE_CONTINUE|self::MODE_CONTINUE_FULL_ROLLBACK|self::MODE_RESET|self::MODE_INIT
* @return void
*/
public function run($mode = self::MODE_CONTINUE)
Expand All @@ -103,15 +104,30 @@ public function run($mode = self::MODE_CONTINUE)
$this->printer->printReset();
}

$this->driver->createTable();
$migrations = $this->driver->getAllMigrations();
$files = $this->finder->find($this->groups, array_keys($this->extensionsHandlers));
$toExecute = $this->orderResolver->resolve($migrations, $this->groups, $files, $mode);
$this->printer->printToExecute($toExecute);

foreach ($toExecute as $file) {
$queriesCount = $this->execute($file);
$this->printer->printExecute($file, $queriesCount);
$this->driver->beginTransaction();
try {
$this->driver->createTable();
$migrations = $this->driver->getAllMigrations();
$files = $this->finder->find($this->groups, array_keys($this->extensionsHandlers));
$toExecute = $this->orderResolver->resolve($migrations, $this->groups, $files, $mode);
$this->printer->printToExecute($toExecute);

foreach ($toExecute as $file) {
$queriesCount = $this->execute($file);
$this->printer->printExecute($file, $queriesCount);
}
$this->driver->commitTransaction();

} catch (\Exception $e) {
if ($mode === self::MODE_CONTINUE_FULL_ROLLBACK) {
// rollback all migrations executed in this run
$this->driver->rollbackTransaction();

} else if ($mode === self::MODE_CONTINUE) {
// commit migrations not including the failing one
$this->driver->commitTransaction();
}
throw $e;
}

$this->driver->unlock();
Expand Down
76 changes: 76 additions & 0 deletions tests/cases/integration/Runner.Rollback.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

/**
* @testCase
* @dataProvider ../../dbals.ini
*/

namespace NextrasTests\Migrations;

use Mockery;
use Nextras\Migrations\Engine\Runner;
use Nextras\Migrations\Entities\Group;
use Tester;
use Tester\Assert;

require __DIR__ . '/../../bootstrap.php';


class RollbackTest extends IntegrationTestCase
{

protected function getGroups($dir)
{
$rollback = new Group();
$rollback->enabled = TRUE;
$rollback->name = 'rollback';
$rollback->directory = $dir . '/rollback';
$rollback->dependencies = [];

return [$rollback];
}

public function testContinueRollbacksFailingOnly()
{
try {
$this->runner->run(Runner::MODE_CONTINUE);
} catch (\Exception $e) {
}

$res = $this->dbal->query('
SELECT Count(*) ' . $this->dbal->escapeIdentifier('count') . ' FROM information_schema.tables
WHERE TABLE_NAME = ' . $this->dbal->escapeString('rollback') . '
AND table_catalog = ' . $this->dbal->escapeString('nextras_migrations_test') . '
AND table_schema = ' . $this->dbal->escapeString($this->dbName) . '
');
$tableExists = (bool) $res[0]['count'];

Assert::true($tableExists);
Assert::count(2, $this->driver->getAllMigrations());
}

public function testFullRollback()
{
$this->driver->createTable();

try {
$this->runner->run(Runner::MODE_CONTINUE_FULL_ROLLBACK);
} catch (\Exception $e) {
}

$res = $this->dbal->query('
SELECT Count(*) ' . $this->dbal->escapeIdentifier('count') . ' FROM information_schema.tables
WHERE TABLE_NAME = ' . $this->dbal->escapeString('rollback') . '
AND table_catalog = ' . $this->dbal->escapeString('nextras_migrations_test') . '
AND table_schema = ' . $this->dbal->escapeString($this->dbName) . '
');
$tableExists = (bool) $res[0]['count'];

Assert::false($tableExists);
Assert::count(0, $this->driver->getAllMigrations());
}

}


(new RollbackTest)->run();
4 changes: 4 additions & 0 deletions tests/fixtures/mysql/rollback/001.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE `rollback` (
`id` bigint NOT NULL,
PRIMARY KEY (`id`)
);
1 change: 1 addition & 0 deletions tests/fixtures/mysql/rollback/002.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
INSERT INTO `rollback` (`id`) VALUES (1), (2), (3);
1 change: 1 addition & 0 deletions tests/fixtures/mysql/rollback/003.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
INSERT INTO `rollback` (`id`) VALUES (3); -- duplicate key
4 changes: 4 additions & 0 deletions tests/fixtures/pgsql/rollback/001.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE "rollback" (
"id" serial4 NOT NULL,
PRIMARY KEY ("id")
);
1 change: 1 addition & 0 deletions tests/fixtures/pgsql/rollback/002.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
INSERT INTO "rollback" ("id") VALUES (1), (2), (3);
1 change: 1 addition & 0 deletions tests/fixtures/pgsql/rollback/003.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
INSERT INTO "rollback" ("id") VALUES (3); -- duplicate key

0 comments on commit e179723

Please sign in to comment.