2011-01-31 19:38:04 +02:00
|
|
|
<?php defined('SYSPATH') or die('No direct script access.');
|
2010-12-25 04:52:51 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Model for managing migrations
|
2011-01-31 19:27:21 +02:00
|
|
|
*/
|
2010-12-25 04:52:51 +02:00
|
|
|
class Model_Minion_Migration extends Model
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Database connection to use
|
|
|
|
* @var Kohana_Database
|
|
|
|
*/
|
|
|
|
protected $_db = NULL;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The table that's used to store the migrations
|
|
|
|
* @var string
|
|
|
|
*/
|
2011-01-21 01:12:06 +02:00
|
|
|
protected $_table = 'minion_migrations';
|
2010-12-25 04:52:51 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs the model, taking a Database connection as the first and only
|
|
|
|
* parameter
|
|
|
|
*
|
|
|
|
* @param Kohana_Database Database connection to use
|
|
|
|
*/
|
|
|
|
public function __construct(Kohana_Database $db)
|
|
|
|
{
|
|
|
|
$this->_db = $db;
|
|
|
|
}
|
|
|
|
|
2011-01-21 01:53:59 +02:00
|
|
|
/**
|
|
|
|
* Returns a list of migrations that are available in the filesystem
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function available_migrations()
|
|
|
|
{
|
|
|
|
$files = Kohana::list_files('migrations');
|
|
|
|
|
|
|
|
return Minion_Migration_Util::compile_migrations_from_files($files);
|
|
|
|
}
|
|
|
|
|
2011-01-21 03:18:08 +02:00
|
|
|
/**
|
|
|
|
* Checks to see if the minion migrations table exists and attempts to
|
|
|
|
* create it if it doesn't
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function ensure_table_exists()
|
|
|
|
{
|
|
|
|
$query = $this->_db->query(Database::SELECT, "SHOW TABLES like '".$this->_table."'");
|
|
|
|
|
2011-01-31 19:27:21 +02:00
|
|
|
if ( ! count($query))
|
2011-01-21 03:18:08 +02:00
|
|
|
{
|
|
|
|
$sql = file_get_contents(Kohana::find_file('', 'minion_schema', 'sql'));
|
|
|
|
|
|
|
|
$this->_db->query(NULL, $sql);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-21 01:53:59 +02:00
|
|
|
/**
|
|
|
|
* Gets the status of all locations, whether they're in the db or not.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function get_location_statuses()
|
|
|
|
{
|
|
|
|
// Start out using all the installed locations
|
|
|
|
$locations = $this->fetch_current_versions('location', 'id');
|
|
|
|
$available = $this->available_migrations();
|
|
|
|
|
2011-01-31 19:27:21 +02:00
|
|
|
foreach ($available as $migration)
|
2011-01-21 01:53:59 +02:00
|
|
|
{
|
2011-01-31 19:27:21 +02:00
|
|
|
if (array_key_exists($migration['id'], $locations))
|
2011-01-21 01:53:59 +02:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$locations[$migration['location']] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $locations;
|
|
|
|
}
|
|
|
|
|
2011-01-10 16:15:07 +02:00
|
|
|
/**
|
|
|
|
* Get or Set the table to use to store migrations
|
|
|
|
*
|
|
|
|
* Should only really be used during testing
|
|
|
|
*
|
|
|
|
* @param string Table name
|
|
|
|
* @return string|Model_Minion_Migration Get table name or return $this on set
|
|
|
|
*/
|
|
|
|
public function table($table = NULL)
|
|
|
|
{
|
2011-01-31 19:27:21 +02:00
|
|
|
if ($table === NULL)
|
2011-01-10 16:15:07 +02:00
|
|
|
return $this->_table;
|
|
|
|
|
|
|
|
$this->_table = $table;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2010-12-25 04:52:51 +02:00
|
|
|
/**
|
|
|
|
* Creates a new select query which includes all fields in the migrations
|
|
|
|
* table plus a `id` field which is a combination of the timestamp and the
|
|
|
|
* description
|
|
|
|
*
|
|
|
|
* @return Database_Query_Builder_Select
|
|
|
|
*/
|
|
|
|
protected function _select()
|
|
|
|
{
|
2010-12-29 05:03:00 +02:00
|
|
|
return DB::select('*', DB::expr('CONCAT(`location`, ":", CAST(`timestamp` AS CHAR)) AS `id`'))->from($this->_table);
|
2010-12-25 04:52:51 +02:00
|
|
|
}
|
|
|
|
|
2010-12-29 06:29:05 +02:00
|
|
|
/**
|
|
|
|
* Inserts a migration into the database
|
|
|
|
*
|
|
|
|
* @param array Migration data
|
|
|
|
* @return Model_Minion_Migration $this
|
|
|
|
*/
|
|
|
|
public function add_migration(array $migration)
|
|
|
|
{
|
|
|
|
DB::insert($this->_table, array('timestamp', 'location', 'description'))
|
|
|
|
->values(array($migration['timestamp'], $migration['location'], $migration['description']))
|
|
|
|
->execute($this->_db);
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2010-12-30 17:04:09 +02:00
|
|
|
/**
|
|
|
|
* Get a migration by its id
|
|
|
|
*
|
|
|
|
* @param string Migration ID
|
|
|
|
* @return array Migration info
|
|
|
|
*/
|
|
|
|
public function get_migration($location, $timestamp = NULL)
|
|
|
|
{
|
2011-01-31 19:27:21 +02:00
|
|
|
if ($timestamp === NULL)
|
2010-12-30 17:04:09 +02:00
|
|
|
{
|
2011-01-31 19:27:21 +02:00
|
|
|
if (empty($location) OR strpos(':', $location) === FALSE)
|
2010-12-30 17:04:09 +02:00
|
|
|
{
|
|
|
|
throw new Kohana_Exception('Invalid migration id :id', array(':id' => $location));
|
|
|
|
}
|
|
|
|
|
|
|
|
list($location, $timestamp) = explode(':', $location);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->_select()
|
|
|
|
->where('timestamp', '=', (string) $timestamp)
|
|
|
|
->where('location', '=', (string) $location)
|
|
|
|
->execute($this->_db)
|
|
|
|
->current();
|
|
|
|
}
|
|
|
|
|
2010-12-29 06:29:05 +02:00
|
|
|
/**
|
|
|
|
* Deletes a migration from the database
|
|
|
|
*
|
|
|
|
* @param string|array Migration id / info
|
|
|
|
* @return Model_Minion_Migration $this
|
|
|
|
*/
|
|
|
|
public function delete_migration($migration)
|
|
|
|
{
|
2011-01-31 19:27:21 +02:00
|
|
|
if (is_array($migration))
|
2010-12-29 06:29:05 +02:00
|
|
|
{
|
|
|
|
$timestamp = $migration['timestamp'];
|
|
|
|
$location = $migration['location'];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
list($timestamp, $location) = explode(':', $migration);
|
|
|
|
}
|
|
|
|
|
|
|
|
DB::delete($this->_table)
|
|
|
|
->where('timestamp', '=', $timestamp)
|
|
|
|
->where('location', '=', $location)
|
|
|
|
->execute($this->_db);
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update an existing migration record to reflect a new one
|
|
|
|
*
|
|
|
|
* @param array The current migration
|
|
|
|
* @param array The new migration
|
|
|
|
* @return Model_Minion_Migration $this
|
|
|
|
*/
|
|
|
|
public function update_migration(array $current, array $new)
|
|
|
|
{
|
|
|
|
$set = array();
|
|
|
|
|
2011-01-31 19:27:21 +02:00
|
|
|
foreach ($new as $key => $value)
|
2010-12-29 06:29:05 +02:00
|
|
|
{
|
2011-01-31 19:27:21 +02:00
|
|
|
if ($key !== 'id' AND $current[$key] !== $value)
|
2010-12-29 06:29:05 +02:00
|
|
|
{
|
|
|
|
$set[$key] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-31 19:27:21 +02:00
|
|
|
if (count($set))
|
2010-12-29 06:29:05 +02:00
|
|
|
{
|
|
|
|
DB::update($this->_table)
|
|
|
|
->set($set)
|
|
|
|
->where('timestamp', '=', $current['timestamp'])
|
|
|
|
->where('location', '=', $current['location'])
|
|
|
|
->execute($this->_db);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2010-12-30 05:51:20 +02:00
|
|
|
/**
|
|
|
|
* Change the applied status for a migration
|
|
|
|
*
|
|
|
|
* @param array Migration information
|
|
|
|
* @param bool Whether this migration has been applied or unapplied
|
|
|
|
* @return Model_Minion_Migration
|
|
|
|
*/
|
|
|
|
public function mark_migration(array $migration, $applied)
|
|
|
|
{
|
|
|
|
DB::update($this->_table)
|
|
|
|
->set(array('applied' => (int) $applied))
|
|
|
|
->where('timestamp', '=', $migration['timestamp'])
|
|
|
|
->where('location', '=', $migration['location'])
|
|
|
|
->execute($this->_db);
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2010-12-25 04:52:51 +02:00
|
|
|
/**
|
|
|
|
* Selects all migrations from the migratinos table
|
|
|
|
*
|
|
|
|
* @return Kohana_Database_Result
|
|
|
|
*/
|
2010-12-29 02:46:07 +02:00
|
|
|
public function fetch_all($key = NULL, $value = NULL)
|
2010-12-25 04:52:51 +02:00
|
|
|
{
|
|
|
|
return $this->_select()
|
2010-12-29 02:46:07 +02:00
|
|
|
->execute($this->_db)
|
|
|
|
->as_array($key, $value);
|
2010-12-25 04:52:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-12-28 04:47:49 +02:00
|
|
|
* Fetches the latest version for all installed locations
|
2010-12-25 04:52:51 +02:00
|
|
|
*
|
2010-12-28 04:47:49 +02:00
|
|
|
* If a location does not have any applied migrations then no result will be
|
2010-12-25 04:52:51 +02:00
|
|
|
* returned for it
|
|
|
|
*
|
|
|
|
* @return Kohana_Database_Result
|
|
|
|
*/
|
2010-12-29 02:46:07 +02:00
|
|
|
public function fetch_current_versions($key = 'location', $value = NULL)
|
2010-12-25 04:52:51 +02:00
|
|
|
{
|
2010-12-28 17:40:54 +02:00
|
|
|
// Little hack needed to do an order by before a group by
|
|
|
|
return DB::select()
|
|
|
|
->from(array(
|
|
|
|
$this->_select()
|
|
|
|
->where('applied', '>', 0)
|
|
|
|
->order_by('timestamp', 'DESC'),
|
|
|
|
'temp_table'
|
|
|
|
))
|
2010-12-28 04:47:49 +02:00
|
|
|
->group_by('location')
|
2010-12-29 02:46:07 +02:00
|
|
|
->execute($this->_db)
|
|
|
|
->as_array($key, $value);
|
2010-12-25 04:52:51 +02:00
|
|
|
}
|
|
|
|
|
2010-12-31 02:49:09 +02:00
|
|
|
/**
|
|
|
|
* Fetches a list of locations
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function fetch_locations($location_as_key = FALSE)
|
|
|
|
{
|
|
|
|
return DB::select()
|
|
|
|
->from($this->_table)
|
|
|
|
->group_by('location')
|
|
|
|
->execute($this->_db)
|
|
|
|
->as_array($location_as_key ? 'location' : NULL, 'location');
|
|
|
|
}
|
|
|
|
|
2010-12-25 04:52:51 +02:00
|
|
|
/**
|
|
|
|
* Fetch a list of migrations that need to be applied in order to reach the
|
|
|
|
* required version
|
|
|
|
*
|
2010-12-28 07:01:22 +02:00
|
|
|
* @param string Migration's location
|
|
|
|
* @param string Target migration id
|
|
|
|
* @param boolean Default direction of versionless migrations
|
2010-12-25 04:52:51 +02:00
|
|
|
*/
|
2010-12-28 19:40:40 +02:00
|
|
|
public function fetch_required_migrations($locations = NULL, $target_version = TRUE, $default_direction = TRUE)
|
2010-12-25 04:52:51 +02:00
|
|
|
{
|
2011-01-31 19:27:21 +02:00
|
|
|
if ( ! empty($locations) AND ! is_array($locations))
|
2010-12-25 04:52:51 +02:00
|
|
|
{
|
2010-12-28 07:01:22 +02:00
|
|
|
$locations = array(
|
2010-12-28 19:40:40 +02:00
|
|
|
$locations => is_array($target_version)
|
|
|
|
? $default_direction
|
|
|
|
: $target_version
|
2010-12-28 07:01:22 +02:00
|
|
|
);
|
2010-12-25 04:52:51 +02:00
|
|
|
}
|
|
|
|
|
2010-12-28 04:47:49 +02:00
|
|
|
// Get an array of the latest migrations, with the location name as the
|
2010-12-25 04:52:51 +02:00
|
|
|
// array key
|
2010-12-29 02:46:07 +02:00
|
|
|
$migrations = $this->fetch_current_versions('location');
|
2010-12-25 04:52:51 +02:00
|
|
|
|
2010-12-28 07:01:22 +02:00
|
|
|
// The user wants to run all available migrations
|
2011-01-31 19:27:21 +02:00
|
|
|
if (empty($locations))
|
2010-12-25 04:52:51 +02:00
|
|
|
{
|
2010-12-31 02:49:09 +02:00
|
|
|
// Fetch a mirrored array of locations => locations
|
|
|
|
$locations = $this->fetch_locations(TRUE);
|
2010-12-25 04:52:51 +02:00
|
|
|
}
|
2010-12-30 05:42:03 +02:00
|
|
|
// If the calling script has been lazy and given us a numerically
|
|
|
|
// indexed array of locations then we need to convert it to a mirrored
|
|
|
|
// array
|
|
|
|
// We will decide the target version for these within the loop below
|
2011-01-31 19:27:21 +02:00
|
|
|
elseif ( ! Arr::is_assoc($locations))
|
2010-12-30 05:42:03 +02:00
|
|
|
{
|
2011-01-31 19:27:21 +02:00
|
|
|
foreach ($locations as $_pos => $location)
|
2010-12-30 05:42:03 +02:00
|
|
|
{
|
|
|
|
unset($locations[$_pos]);
|
|
|
|
|
|
|
|
$locations[$location] = $location;
|
|
|
|
}
|
|
|
|
}
|
2010-12-25 04:52:51 +02:00
|
|
|
|
2010-12-28 07:01:22 +02:00
|
|
|
// Merge locations with specified target versions
|
2011-01-31 19:27:21 +02:00
|
|
|
if ( ! empty($target_version) AND is_array($target_version))
|
2010-12-28 07:01:22 +02:00
|
|
|
{
|
2010-12-28 19:40:40 +02:00
|
|
|
$locations = $target_version + $locations;
|
2010-12-28 07:01:22 +02:00
|
|
|
}
|
|
|
|
|
2010-12-25 04:52:51 +02:00
|
|
|
$migrations_to_apply = array();
|
|
|
|
|
|
|
|
// What follows is a bit of icky code, but there aren't many "nice" ways around it
|
2011-01-31 19:27:21 +02:00
|
|
|
|
2010-12-25 04:52:51 +02:00
|
|
|
// Basically we need to get a list of migrations that need to be performed, but
|
|
|
|
// the ordering of the migrations varies depending on whether we're wanting to
|
|
|
|
// migrate up or migrate down. As such, we can't just apply a generic "order by x"
|
2010-12-28 04:47:49 +02:00
|
|
|
// condition, we have to run an individual query for each location
|
2011-01-31 19:27:21 +02:00
|
|
|
|
2010-12-25 05:21:43 +02:00
|
|
|
// Again, icky, but this appears to be the only "sane" way of doing it with multiple
|
2010-12-28 04:47:49 +02:00
|
|
|
// locations
|
2011-01-31 19:27:21 +02:00
|
|
|
|
2010-12-25 04:52:51 +02:00
|
|
|
// If you have a better way of doing this, please let me know :)
|
|
|
|
|
2011-01-31 19:27:21 +02:00
|
|
|
foreach ($locations as $location => $target)
|
2010-12-25 04:52:51 +02:00
|
|
|
{
|
2010-12-25 05:12:18 +02:00
|
|
|
// By default all migrations go "up"
|
2010-12-28 07:01:22 +02:00
|
|
|
$migrations_to_apply[$location]['direction'] = TRUE;
|
2010-12-28 04:47:49 +02:00
|
|
|
$migrations_to_apply[$location]['migrations'] = array();
|
2010-12-25 05:12:18 +02:00
|
|
|
|
2010-12-28 05:29:57 +02:00
|
|
|
$query = $this->_select()->where('location', '=', $location);
|
2010-12-25 04:52:51 +02:00
|
|
|
|
2010-12-28 07:01:22 +02:00
|
|
|
// If this migration was auto-selected from the db then use the
|
|
|
|
// default migration direction
|
2011-01-31 19:27:21 +02:00
|
|
|
if ($target === $location)
|
2010-12-28 07:01:22 +02:00
|
|
|
{
|
2010-12-28 19:40:40 +02:00
|
|
|
$target = is_bool($target_version)
|
|
|
|
? $target_version
|
|
|
|
: (bool) $default_direction;
|
2010-12-28 07:01:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the user is rolling this location to either extreme up or
|
|
|
|
// extreme down
|
2011-01-31 19:27:21 +02:00
|
|
|
if (is_bool($target))
|
2010-12-25 05:12:18 +02:00
|
|
|
{
|
2010-12-28 05:29:57 +02:00
|
|
|
// We're "undoing" all applied migrations, i.e. rolling back
|
2011-01-31 19:27:21 +02:00
|
|
|
if ($target === FALSE)
|
2010-12-28 05:29:57 +02:00
|
|
|
{
|
2010-12-28 07:01:22 +02:00
|
|
|
$migrations_to_apply[$location]['direction'] = FALSE;
|
2010-12-28 05:29:57 +02:00
|
|
|
|
|
|
|
$query
|
|
|
|
->where('applied', '=', 1)
|
|
|
|
->order_by('timestamp', 'DESC');
|
|
|
|
}
|
|
|
|
// We're rolling forward
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$query
|
|
|
|
->where('applied', '=', 0)
|
|
|
|
->order_by('timestamp', 'ASC');
|
|
|
|
}
|
2010-12-25 05:12:18 +02:00
|
|
|
}
|
|
|
|
// Else if the user explicitly specified a target version of some kind
|
|
|
|
else
|
2010-12-25 04:52:51 +02:00
|
|
|
{
|
2010-12-29 05:03:00 +02:00
|
|
|
$timestamp = $target;
|
|
|
|
$current_timestamp = isset($migrations[$location])
|
2010-12-28 07:01:22 +02:00
|
|
|
? $migrations[$location]['timestamp']
|
|
|
|
: NULL;
|
2010-12-25 04:52:51 +02:00
|
|
|
|
2010-12-28 07:01:22 +02:00
|
|
|
// If the current version is the requested version then nothing
|
|
|
|
// needs to be done
|
2011-01-31 19:27:21 +02:00
|
|
|
if ($current_timestamp === $timestamp)
|
2010-12-25 04:52:51 +02:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2010-12-28 04:47:49 +02:00
|
|
|
// If they haven't applied any migrations for this location
|
2010-12-28 07:01:22 +02:00
|
|
|
// yet and are justwhere wanting to apply all migrations
|
|
|
|
// (i.e. roll forward)
|
2011-01-31 19:27:21 +02:00
|
|
|
if ($current_timestamp === NULL)
|
2010-12-25 04:52:51 +02:00
|
|
|
{
|
|
|
|
$query
|
2010-12-25 05:12:18 +02:00
|
|
|
->and_where('timestamp', '<=', $timestamp)
|
|
|
|
->order_by('timestamp', 'ASC');
|
2010-12-25 04:52:51 +02:00
|
|
|
}
|
|
|
|
// If we need to move forward
|
2011-01-31 19:27:21 +02:00
|
|
|
elseif ($timestamp > $current_timestamp)
|
2010-12-25 04:52:51 +02:00
|
|
|
{
|
|
|
|
$query
|
2010-12-28 05:29:57 +02:00
|
|
|
->and_where('timestamp', '<=', $timestamp)
|
2010-12-25 05:12:18 +02:00
|
|
|
->and_where('applied', '=', 0)
|
|
|
|
->order_by('timestamp', 'ASC');
|
2010-12-25 04:52:51 +02:00
|
|
|
}
|
|
|
|
// If we want to roll back
|
2011-01-31 19:27:21 +02:00
|
|
|
elseif ($timestamp < $current_timestamp)
|
2010-12-25 04:52:51 +02:00
|
|
|
{
|
|
|
|
$query
|
2010-12-25 05:00:34 +02:00
|
|
|
->and_where('timestamp', '<', $current_timestamp)
|
2010-12-28 05:29:57 +02:00
|
|
|
->and_where('timestamp', '>', $timestamp)
|
2010-12-25 05:12:18 +02:00
|
|
|
->and_where('applied', '=', 1)
|
|
|
|
->order_by('timestamp', 'DESC');
|
|
|
|
|
2010-12-28 07:01:22 +02:00
|
|
|
$migrations_to_apply[$location]['direction'] = FALSE;
|
2010-12-25 04:52:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-28 07:01:22 +02:00
|
|
|
$migrations_to_apply[$location]['migrations'] = $query->execute($this->_db)->as_array();
|
2010-12-25 05:12:18 +02:00
|
|
|
|
2010-12-25 04:52:51 +02:00
|
|
|
unset($query);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $migrations_to_apply;
|
|
|
|
}
|
|
|
|
}
|