diff --git a/classes/minion/migration/manager.php b/classes/minion/migration/manager.php
index e2a70ff..df7954d 100644
--- a/classes/minion/migration/manager.php
+++ b/classes/minion/migration/manager.php
@@ -118,91 +118,60 @@ class Minion_Migration_Manager {
/**
* Run migrations in the specified groups so as to reach specified targets
*
- * There are three methods for specifying target versions:
*
- * 1. Pass them in with the array of groups, i.e.
- *
- * array(
- * group => target_version
- * )
- *
- * 2. Pass them in separately, with param1 containing an array of
- * groups like:
- *
- * array(
- * group,
- * group2,
- * )
- *
- * And param2 containing an array structured in the same way as in #1
- *
- * 3. Perform a mix of the above two methods
- *
- * It may seem odd to use two arrays to specify groups and versions, but
- * it's this way to allow users to upgrade / downgrade all groups while
- * migrating a specific group to a specific version
- *
- * If no groups are specified then migrations from all groups will be
- * run and be brought up to the latest available version
*
* @param array Set of groups to update, empty array means all
* @param array Versions for specified groups
- * @param boolean The default direction (up/down) for migrations without a specific version
* @return array Array of all migrations that were successfully applied
*/
- public function run_migration(array $groups = array(), $versions = array(), $default_direction = TRUE)
+ public function run_migration($group = array(), $target = TRUE)
{
- $migrations = $this->_model->fetch_required_migrations($groups, $versions, $default_direction);
+ list($migrations, $is_up) = $this->_model->fetch_required_migrations($group, $target);
- foreach ($migrations as $path => $group)
+ $method = $is_up ? 'up' : 'down';
+
+ foreach ($migrations as $migration)
{
- $method = $group['direction'] ? 'up' : 'down';
+ $filename = Minion_Migration_Util::get_filename_from_migration($migration);
- foreach ($group['migrations'] as $migration)
+ if ( ! ($file = Kohana::find_file('migrations', $filename, FALSE)))
{
- $filename = Minion_Migration_Util::get_filename_from_migration($migration);
-
- if ( ! ($file = Kohana::find_file('migrations', $filename, FALSE)))
- {
- throw new Kohana_Exception(
- 'Cannot load migration :migration (:file)',
- array(
- ':migration' => $migration['id'],
- ':file' => $filename
- )
- );
- }
-
- $class = Minion_Migration_Util::get_class_from_migration($migration);
-
-
- include_once $file;
-
- $instance = new $class($migration);
-
- $db = $this->_get_db_instance($instance->get_database_connection());
-
- try
- {
- $instance->$method($db);
- }
- catch(Database_Exception $e)
- {
- throw new Minion_Migration_Exception($e->getMessage(), $migration);
- }
-
-
- if ($this->_dry_run)
- {
- $this->_dry_run_sql[$path][$migration['timestamp']] = $db->reset_query_stack();
- }
- else
- {
- $this->_model->mark_migration($migration, $group['direction']);
- }
-
- $this->_executed_migrations[] = $migration;
+ throw new Kohana_Exception(
+ 'Cannot load migration :migration (:file)',
+ array(
+ ':migration' => $migration['id'],
+ ':file' => $filename
+ )
+ );
}
+
+ $class = Minion_Migration_Util::get_class_from_migration($migration);
+
+ include_once $file;
+
+ $instance = new $class($migration);
+
+ $db = $this->_get_db_instance($instance->get_database_connection());
+
+ try
+ {
+ $instance->$method($db);
+ }
+ catch(Database_Exception $e)
+ {
+ throw new Minion_Migration_Exception($e->getMessage(), $migration);
+ }
+
+ if ($this->_dry_run)
+ {
+ $this->_dry_run_sql[$path][$migration['timestamp']] = $db->reset_query_stack();
+ }
+ else
+ {
+ $this->_model->mark_migration($migration, $is_up);
+ }
+
+ $this->_executed_migrations[] = $migration;
}
}
@@ -244,6 +213,9 @@ class Minion_Migration_Manager {
{
$this->_model->update_migration($installed[$migration], $available[$migration]);
}
+ {
+
+ }
}
diff --git a/classes/minion/task/db/migrate.php b/classes/minion/task/db/migrate.php
index 73766cd..3c08c43 100644
--- a/classes/minion/task/db/migrate.php
+++ b/classes/minion/task/db/migrate.php
@@ -7,27 +7,21 @@
*
* Available config options are:
*
- * --versions=[group:]version
+ * --migrate-down
*
- * Used to specify the version to migrate the database to. The group prefix
- * is used to specify the target version of an individual group. Version
- * specifies the target version, which can be either:
+ * Migrate the group(s) down
*
- * * A migration version (migrates up/down to that version)
- * * TRUE (runs all migrations to get to the latest version)
- * * FALSE (undoes all appled migrations)
+ * --migrate-up
*
- * An example of a migration version is 20101229015800
+ * Migrate the group(s) up
*
- * If you specify TRUE / FALSE without a group then the default migration
- * direction for groups without a specified version will be up / down respectively.
+ * --migrate-to=(timestamp|+up_migrations|down_migrations)
*
- * If you're only specifying a migration version then you *must* specify a group
+ * Migrate to a specific timestamp, or up $up_migrations, or down $down_migrations
*
* --groups=group[,group2[,group3...]]
*
- * A list of groups (under the migrations folder in the cascading
- * filesystem) that will be used to source migration files. By default
+ * A list of groups that will be used to source migration files. By default
* migrations will be loaded from all available groups
*
* --dry-run
@@ -44,20 +38,16 @@
*/
class Minion_Task_Db_Migrate extends Minion_Task
{
-
- /*
- * The default direction for migrations, TRUE = up, FALSE = down
- * @var boolean
- */
- protected $_default_direction = TRUE;
-
/**
* A set of config options that this task accepts
* @var array
*/
protected $_config = array(
- 'versions',
+ 'group',
'groups',
+ 'migrate-up',
+ 'migrate-down',
+ 'migrate-to',
'dry-run',
'quiet'
);
@@ -71,19 +61,36 @@ class Minion_Task_Db_Migrate extends Minion_Task
{
$k_config = Kohana::config('minion/migration');
- // Grab user input, using sensible defaults
- $specified_groups = Arr::get($config, 'groups', NULL);
- $versions = Arr::get($config, 'versions', NULL);
- $dry_run = array_key_exists('dry-run', $config);
- $quiet = array_key_exists('quiet', $config);
+ $groups = Arr::get($config, 'group', Arr::get($config, 'groups', NULL));
+ $target = Arr::get($config, 'migrate-to', NULL);
- $targets = $this->_parse_target_versions($versions);
- $groups = $this->_parse_groups($specified_groups);
+ $dry_run = array_key_exists('dry-run', $config);
+ $quiet = array_key_exists('quiet', $config);
+ $up = array_key_exists('migrate-up', $config);
+ $down = array_key_exists('migrate-down', $config);
+
+ $groups = $this->_parse_groups($groups);
+
+ if ($target === NULL)
+ {
+ if ($up)
+ {
+ $target = TRUE;
+ }
+ elseif ($down)
+ {
+ $target = FALSE;
+ }
+ else
+ {
+ echo "No migration target specified\n";
+ return;
+ }
+ }
$db = Database::instance();
$model = new Model_Minion_Migration($db);
-
$model->ensure_table_exists();
$manager = new Minion_Migration_Manager($db, $model);
@@ -91,13 +98,12 @@ class Minion_Task_Db_Migrate extends Minion_Task
$manager
// Sync the available migrations with those in the db
->sync_migration_files()
-
->set_dry_run($dry_run);
try
{
// Run migrations for specified groups & versions
- $manager->run_migration($groups, $targets, $this->_default_direction);
+ $manager->run_migration($groups, $target);
}
catch(Minion_Migration_Exception $e)
{
@@ -106,7 +112,6 @@ class Minion_Task_Db_Migrate extends Minion_Task
->set('error', $e->getMessage());
}
-
$view = View::factory('minion/task/db/migrate')
->set('dry_run', $dry_run)
->set('quiet', $quiet)
diff --git a/classes/model/minion/migration.php b/classes/model/minion/migration.php
index eb82211..c945bbe 100644
--- a/classes/model/minion/migration.php
+++ b/classes/model/minion/migration.php
@@ -66,12 +66,12 @@ class Model_Minion_Migration extends Model
public function get_group_statuses()
{
// Start out using all the installed groups
- $groups = $this->fetch_current_versions('group', 'id');
+ $groups = $this->fetch_current_versions('group');
$available = $this->available_migrations();
foreach ($available as $migration)
{
- if (array_key_exists($migration['id'], $groups))
+ if (array_key_exists($migration['group'], $groups))
{
continue;
}
@@ -283,12 +283,13 @@ class Model_Minion_Migration extends Model
* @param string The groups to get migrations for
* @param mixed Target version
*/
- public function fetch_required_migrations($group = NULL, $target = TRUE)
+ public function fetch_required_migrations(array $group, $target = TRUE)
{
$migrations = array();
$current_groups = $this->fetch_groups(TRUE);
- foreach ( (array) $group as $group_name)
+ // Make sure the group(s) exist
+ foreach ($group as $group_name)
{
if ( ! isset($current_groups[$group_name]))
{
@@ -303,25 +304,46 @@ class Model_Minion_Migration extends Model
$up = $target;
// If we want to limit this migration to certain groups
- if ($group !== NULL)
+ if ( ! empty($group))
{
- if (is_array($group))
+ if (count($group) > 1)
{
$query->where('group', 'IN', $group);
}
else
{
- $query->where('group', '=', $group);
+ $query->where('group', '=', $group[0]);
}
}
}
- else
+ // Relative up/down target
+ elseif (in_array($target[0], array('+', '-')))
{
list($target, $up) = $this->resolve_target($group, $target);
$query->where('group', '=', $group);
- if ($up)
+ if( $target !== NULL)
+ {
+ if ($up)
+ {
+ $query->where('timestamp', '<=', $target);
+ }
+ else
+ {
+ $query->where('timestamp', '>=', $target);
+ }
+ }
+
+ }
+ // Absolute timestamp
+ else
+ {
+ $query->where('group', '=', $group);
+
+ $statuses = $this->fetch_current_versions();
+
+ if ($up = ($statuses[$group[0]] < $target))
{
$query->where('timestamp', '<=', $target);
}
@@ -342,12 +364,13 @@ class Model_Minion_Migration extends Model
else
{
$query
- ->where('applied', '>', 0)
+ ->where('applied', '=', 1)
->order_by('timestamp', 'DESC');
}
+ //var_dump($query->compile($this->_db));
- return $query->execute($this->_db)->as_array();
+ return array($query->execute($this->_db)->as_array(), $up);
}
/**
@@ -374,82 +397,56 @@ class Model_Minion_Migration extends Model
$group = $group[0];
}
- $amount = NULL;
+ if( ! in_array($target[0], array('+', '-')))
+ {
+ throw new Kohana_Exception("Invalid relative target");
+ }
+
$query = $this->_select();
$statuses = $this->fetch_current_versions();
$target = (string) $target;
$group_applied = isset($statuses[$group]);
- $up = NULL;
$timestamp = $group_applied ? $statuses[$group]['timestamp'] : NULL;
-
- // If this target is relative to the current state of the group
- if ($target[0] === '+' OR $target[0] === '-')
- {
- $amount = substr($target, 1);
- $up = $target[0] === '+';
-
- if ($up)
- {
- if ($group_applied)
- {
- $query->where('timestamp', '>', $timestamp);
- }
- }
- else
- {
- if ( ! $group_applied)
- {
- throw new Kohana_Exception(
- "Cannot migrate group :group down as none of its migrations have been applied",
- array(':group' => $group)
- );
- }
-
- $query->where('timestamp', '<=', $timestamp);
- }
-
- $query->limit($amount);
- }
- // Else this is an absolute target
- else
+ $amount = substr($target, 1);
+ $up = $target[0] === '+';
+
+ if ($up)
{
if ($group_applied)
{
- $up = ( (int) $timestamp < (int) $target);
-
- $query->where('timestamp', ($up ? '>' : '<='), $timestamp);
- }
- else
- {
- $up = TRUE;
- }
-
- if ($up)
- {
- $query->where('timestamp', '<=', $target);
- }
- else
- {
- $query->where('timestamp', '>', $target);
+ $query->where('timestamp', '>', $timestamp);
}
}
-
- if ( ! $up)
+ else
{
- $query->where('applied', '>', 0);
+ if ( ! $group_applied)
+ {
+ throw new Kohana_Exception(
+ "Cannot migrate group :group down as none of its migrations have been applied",
+ array(':group' => $group)
+ );
+ }
+
+ $query
+ ->where('applied', '=', 1)
+ ->where('timestamp', '<=', $timestamp);
}
+ $query->limit($amount);
+
$query->where('group', '=', $group);
$query->order_by('timestamp', ($up ? 'ASC' : 'DESC'));
$results = $query->execute($this->_db);
+ //var_dump($query->compile($this->_db));
+
if ($amount !== NULL AND count($results) != $amount)
{
return array(NULL, $up);
}
- return array((float) $query->execute($this->_db)->get('timestamp'), $up);
+ return array((string) $query->execute($this->_db)->get('timestamp'), $up);
}
}
diff --git a/tests/datasets/minion/migration/model.xml b/tests/datasets/minion/migration/model.xml
index c50e780..da73bf1 100644
--- a/tests/datasets/minion/migration/model.xml
+++ b/tests/datasets/minion/migration/model.xml
@@ -8,4 +8,9 @@
+
+
+
+
+
diff --git a/tests/minion/migration/model.php b/tests/minion/migration/model.php
index 6e5797f..6bbf4dd 100644
--- a/tests/minion/migration/model.php
+++ b/tests/minion/migration/model.php
@@ -66,7 +66,7 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
{
$migrations = $this->getModel()->fetch_all();
- $this->assertSame(7, count($migrations));
+ $this->assertSame(10, count($migrations));
}
/**
@@ -84,6 +84,7 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
array (
'app' => '20101216080000',
'dblogger' => '20101225000000',
+ 'done' => '20101546112100',
),
$versions
);
@@ -101,19 +102,22 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
array(
array(
array(
- 'timestamp' => '20101215165000',
- 'description' => 'add-name-column-to-members',
- 'group' => 'app',
- 'applied' => '0',
- 'id' => 'app:20101215165000',
- ),
- array(
- 'timestamp' => '20101216000000',
- 'description' => 'add-index-on-name',
- 'group' => 'app',
- 'applied' => '0',
- 'id' => 'app:20101216000000',
+ array(
+ 'timestamp' => '20101215165000',
+ 'description' => 'add-name-column-to-members',
+ 'group' => 'app',
+ 'applied' => '0',
+ 'id' => 'app:20101215165000',
+ ),
+ array(
+ 'timestamp' => '20101216000000',
+ 'description' => 'add-index-on-name',
+ 'group' => 'app',
+ 'applied' => '0',
+ 'id' => 'app:20101216000000',
+ ),
),
+ TRUE
),
'app',
TRUE,
@@ -122,19 +126,22 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
array(
array(
array(
- 'timestamp' => '20101216080000',
- 'description' => 'remove-password-salt-column',
- 'group' => 'app',
- 'applied' => '1',
- 'id' => 'app:20101216080000',
- ),
- array(
- 'timestamp' => '20101215164400',
- 'description' => 'create-tables',
- 'group' => 'app',
- 'applied' => '1',
- 'id' => 'app:20101215164400',
+ array(
+ 'timestamp' => '20101216080000',
+ 'description' => 'remove-password-salt-column',
+ 'group' => 'app',
+ 'applied' => '1',
+ 'id' => 'app:20101216080000',
+ ),
+ array(
+ 'timestamp' => '20101215164400',
+ 'description' => 'create-tables',
+ 'group' => 'app',
+ 'applied' => '1',
+ 'id' => 'app:20101215164400',
+ ),
),
+ FALSE
),
'app',
FALSE
@@ -143,26 +150,36 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
array(
array(
array(
- 'timestamp' => '20101215165000',
- 'description' => 'add-name-column-to-members',
- 'group' => 'app',
- 'applied' => '0',
- 'id' => 'app:20101215165000',
- ),
- array(
- 'timestamp' => '20101216000000',
- 'description' => 'add-index-on-name',
- 'group' => 'app',
- 'applied' => '0',
- 'id' => 'app:20101216000000',
- ),
- array(
- 'timestamp' => '20101226112100',
- 'description' => 'add-pk',
- 'group' => 'dblogger',
- 'applied' => '0',
- 'id' => 'dblogger:20101226112100',
+ array(
+ 'timestamp' => '20101215165000',
+ 'description' => 'add-name-column-to-members',
+ 'group' => 'app',
+ 'applied' => '0',
+ 'id' => 'app:20101215165000',
+ ),
+ array(
+ 'timestamp' => '20101216000000',
+ 'description' => 'add-index-on-name',
+ 'group' => 'app',
+ 'applied' => '0',
+ 'id' => 'app:20101216000000',
+ ),
+ array(
+ 'timestamp' => '20101226112100',
+ 'description' => 'add-pk',
+ 'group' => 'dblogger',
+ 'applied' => '0',
+ 'id' => 'dblogger:20101226112100',
+ ),
+ array(
+ 'timestamp' => '20101526112100',
+ 'description' => 'add-col',
+ 'group' => 'minion',
+ 'applied' => '0',
+ 'id' => 'minion:20101526112100',
+ ),
),
+ TRUE
),
NULL,
TRUE
@@ -171,57 +188,104 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
array(
array(
array(
- 'timestamp' => '20101225000000',
- 'description' => 'remove-unique-index',
- 'group' => 'dblogger',
- 'applied' => '1',
- 'id' => 'dblogger:20101225000000',
- ),
- array(
- 'timestamp' => '20101216080000',
- 'description' => 'remove-password-salt-column',
- 'group' => 'app',
- 'applied' => '1',
- 'id' => 'app:20101216080000',
- ),
- array(
- 'timestamp' => '20101215164500',
- 'description' => 'create-table',
- 'group' => 'dblogger',
- 'applied' => '1',
- 'id' => 'dblogger:20101215164500',
- ),
- array(
- 'timestamp' => '20101215164400',
- 'description' => 'create-tables',
- 'group' => 'app',
- 'applied' => '1',
- 'id' => 'app:20101215164400',
+ array(
+ 'timestamp' => '20101546112100',
+ 'description' => 'add-bbb',
+ 'group' => 'done',
+ 'applied' => '1',
+ 'id' => 'done:20101546112100'
+ ),
+ array(
+ 'timestamp' => '20101536112100',
+ 'description' => 'add-aaa',
+ 'group' => 'done',
+ 'applied' => '1',
+ 'id'=> 'done:20101536112100'
+ ),
+ array(
+ 'timestamp' => '20101225000000',
+ 'description' => 'remove-unique-index',
+ 'group' => 'dblogger',
+ 'applied' => '1',
+ 'id' => 'dblogger:20101225000000',
+ ),
+ array(
+ 'timestamp' => '20101216080000',
+ 'description' => 'remove-password-salt-column',
+ 'group' => 'app',
+ 'applied' => '1',
+ 'id' => 'app:20101216080000',
+ ),
+ array(
+ 'timestamp' => '20101215164500',
+ 'description' => 'create-table',
+ 'group' => 'dblogger',
+ 'applied' => '1',
+ 'id' => 'dblogger:20101215164500',
+ ),
+ array(
+ 'timestamp' => '20101215164400',
+ 'description' => 'create-tables',
+ 'group' => 'app',
+ 'applied' => '1',
+ 'id' => 'app:20101215164400',
+ ),
),
+ FALSE,
),
NULL,
FALSE
),
+ // Test migrating down to a specific version
array(
array(
array(
- 'timestamp' => '20101216080000',
- 'description' => 'remove-password-salt-column',
- 'group' => 'app',
- 'applied' => '1',
- 'id' => 'app:20101216080000',
- ),
- array(
- 'timestamp' => '20101215164400',
- 'description' => 'create-tables',
- 'group' => 'app',
- 'applied' => '1',
- 'id' => 'app:20101215164400',
+ array(
+ 'timestamp' => '20101216080000',
+ 'description' => 'remove-password-salt-column',
+ 'group' => 'app',
+ 'applied' => '1',
+ 'id' => 'app:20101216080000',
+ ),
),
+ FALSE
),
'app',
- 20101216080000
+ 20101215164400
),
+ // Test migrating up from nothing to everything
+ array(
+ array(
+ array(
+ array(
+ 'timestamp' => '20101526112100',
+ 'description' => 'add-col',
+ 'group' => 'minion',
+ 'applied' => '0',
+ 'id' => 'minion:20101526112100',
+ ),
+ ),
+ TRUE
+ ),
+ 'minion',
+ '+100'
+ ),
+ array(
+ array(
+ array(
+ array(
+ 'timestamp' => '20101546112100',
+ 'description' => 'add-bbb',
+ 'group' => 'done',
+ 'applied' => '1',
+ 'id' => 'done:20101546112100'
+ ),
+ ),
+ FALSE
+ ),
+ 'done',
+ '-1'
+ )
);
}
@@ -239,7 +303,7 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
{
$this->assertSame(
$expected,
- $this->getModel()->fetch_required_migrations($group, $target)
+ $this->getModel()->fetch_required_migrations((array) $group, $target)
);
}
@@ -385,7 +449,7 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
{
return array(
array(
- array(20101216080000, FALSE),
+ array('20101216080000', FALSE),
'app',
'-1'
),
@@ -395,7 +459,7 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
'-10'
),
array(
- array(20101226112100, TRUE),
+ array('20101226112100', TRUE),
'dblogger',
'+1',
),
@@ -428,7 +492,7 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
{
$this->assertSame(
$expected,
- $this->getModel()->resolve_target($group, $target)
+ $this->getModel()->resolve_target((array) $group, $target)
);
}
}
diff --git a/views/minion/task/db/status.php b/views/minion/task/db/status.php
index e6f0170..f9a17f4 100644
--- a/views/minion/task/db/status.php
+++ b/views/minion/task/db/status.php
@@ -1,4 +1,4 @@
$status): ?>
- *
+ *