From: Chuck Hagenbuch Date: Mon, 16 Feb 2009 20:05:08 +0000 (-0500) Subject: initial port of Mad migration libs X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=1935513c6de207fe122c4665fc41e66547e666ee;p=horde.git initial port of Mad migration libs --- diff --git a/framework/Db/lib/Horde/Db/Migration/Base.php b/framework/Db/lib/Horde/Db/Migration/Base.php new file mode 100644 index 000000000..b569cdd0f --- /dev/null +++ b/framework/Db/lib/Horde/Db/Migration/Base.php @@ -0,0 +1,161 @@ + + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Migration + */ + +/** + * @author Mike Naberezny + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Migration + */ +class Horde_Db_Migration_Base +{ + /** + * Print messages as migrations happen + * @var boolean + */ + public static $verbose = true; + + /** + * The migration version + * @var integer + */ + public $version = null; + + protected $_connection; + + + /*########################################################################## + # Constructor + ##########################################################################*/ + + /** + */ + public function __construct($context) + { + $this->version = $context['version']; + $this->_connection = $context['connection']; + } + + + /*########################################################################## + # Public + ##########################################################################*/ + + /** + * Proxy methods over to the connection + * @param string $method + * @param array $args + */ + public function __call($method, $args) + { + foreach ($args as $arg) { + if (is_array($arg)) { + $vals = array(); + foreach ($arg as $key => $value) { + $vals[] = "$key => " . var_export($value, true); + } + $a[] = 'array(' . implode(', ', $vals) . ')'; + } else { + $a[] = $arg; + } + } + $this->say("$method(" . implode(", ", $a) . ")"); + + // benchmark method call + $t = new Horde_Support_Timer(); + $t->start(); + $result = call_user_func_array(array($this->_connection, $method), $args); + $time = $t->finish(); + + // print stats + $this->say(sprintf("%.4fs", $time), 'subitem'); + if (is_int($result)) { + $this->say("$result rows", 'subitem'); + } + + return $result; + } + + public function upWithBechmarks() + { + $this->migrate('up'); + } + + public function downWithBenchmarks() + { + $this->migrate('down'); + } + + /** + * Execute this migration in the named direction + */ + public function migrate($direction) + { + if (!method_exists($this, $direction)) { return; } + + if ($direction == 'up') { $this->announce("migrating"); } + if ($direction == 'down') { $this->announce("reverting"); } + + $result = null; + $t = new Horde_Support_Timer(); + $t->start(); + $result = $this->$direction(); + $time = $t->finish(); + + if ($direction == 'up') { + $this->announce("migrated (" . sprintf("%.4fs", $time) . ")"); + $this->write(); + } + if ($direction == 'down') { + $this->announce("reverted (" . sprintf("%.4fs", $time) . ")"); + $this->write(); + } + return $result; + } + + /** + * @param string $text + */ + public function write($text = '') + { + if (self::$verbose) { + echo "$text\n"; + } + } + + /** + * Announce migration + * @param string $message + */ + public function announce($message) + { + $text = "$this->version " . get_class($this) . ": $message"; + $length = 75 - strlen($text) > 0 ? 75 - strlen($text) : 0; + + $this->write(sprintf("== %s %s", $text, str_repeat('=', $length))); + } + + /** + * @param string $message + * @param boolean $subitem + */ + public function say($message, $subitem = false) + { + $this->write(($subitem ? " ->" : "--") . " $message"); + } + +} diff --git a/framework/Db/lib/Horde/Db/Migration/Exception.php b/framework/Db/lib/Horde/Db/Migration/Exception.php new file mode 100644 index 000000000..314643c70 --- /dev/null +++ b/framework/Db/lib/Horde/Db/Migration/Exception.php @@ -0,0 +1,26 @@ + + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Migration + */ + +/** + * @author Mike Naberezny + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Migration + */ +class Horde_Db_Migration_Exception extends Horde_Db_Exception +{ +} diff --git a/framework/Db/lib/Horde/Db/Migration/Migrator.php b/framework/Db/lib/Horde/Db/Migration/Migrator.php new file mode 100644 index 000000000..a1f0c920e --- /dev/null +++ b/framework/Db/lib/Horde/Db/Migration/Migrator.php @@ -0,0 +1,265 @@ + + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Migration + */ + +/** + * @author Mike Naberezny + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Migration + */ +class Horde_Db_Migration_Migrator +{ + /** + * @var string + */ + protected $_direction = null; + + /** + * @var string + */ + protected $_migrationsPath = null; + + /** + * @var int + */ + protected $_targetVersion = null; + + + /*########################################################################## + # Constructor + ##########################################################################*/ + + /** + * @param string $direction + * @param string $migrationsPath + * @param int $targetVersion + */ + public function __construct($connection, $migrationsPath) + { + if (!$connection->supportsMigrations()) { + $msg = 'This database does not yet support migrations'; + throw new Horde_Db_Migration_Exception($msg); + } + + $this->_connection = $connection; + $this->_migrationsPath = $migrationsPath; + $this->_logger = $logger; + + $this->_connection->initializeSchemaInformation(); + } + + + /*########################################################################## + # Public + ##########################################################################*/ + + /** + * @param string $targetVersion + */ + public function migrate($targetVersion = null) + { + $currentVersion = $this->getCurrentVersion(); + + if ($targetVersion == null || $currentVersion < $targetVersion) { + $this->up($targetVersion); + + // migrate down + } elseif ($currentVersion > $targetVersion) { + $this->down($targetVersion); + + // You're on the right version + } elseif ($currentVersion == $targetVersion) { + return; + } + } + + /** + * @param string $targetVersion + */ + public function up($targetVersion = null) + { + if (!is_null($targetVersion)) { + $this->_targetVersion = $targetVersion; + } + $this->_direction = 'up'; + $this->_doMigrate(); + } + + /** + * @param string $targetVersion + */ + public function down($targetVersion = null) + { + if (!is_null($targetVersion)) { + $this->_targetVersion = $targetVersion; + } + $this->_direction = 'down'; + $this->_doMigrate(); + } + + /** + * @return int + */ + public function getCurrentVersion() + { + $sql = 'SELECT version FROM schema_info'; + return $this->_connection->selectValue($sql); + } + + + /*########################################################################## + # Protected + ##########################################################################*/ + + /** + * Perform migration + */ + protected function _doMigrate() + { + foreach ($this->_getMigrationClasses() as $migration) { + if ($this->_hasReachedTargetVersion($migration->version)) { + $msg = "Reached target version: $this->_targetVersion"; + $this->_logger->info($msg); + return; + } + if ($this->_isIrrelevantMigration($migration->version)) { continue; } + + // log + $msg = "Migrating to ".get_class($migration)." (".$migration->version.")"; + $this->_logger->info($msg); + + // migrate + $migration->migrate($this->_direction); + $this->_setSchemaVersion($migration->version); + } + } + + /** + * @return array + */ + protected function _getMigrationClasses() + { + $migrations = array(); + foreach ($this->_getMigrationFiles() as $migrationFile) { + require_once $migrationFile; + list($version, $name) = $this->_getMigrationVersionAndName($migrationFile); + $this->_assertUniqueMigrationVersion($migrations, $version); + $migrations[$version] = $this->_getMigrationClass($name, $version); + } + + // sort by version + ksort($migrations); + $sorted = array_values($migrations); + return $this->_isDown() ? array_reverse($sorted) : $sorted; + } + + /** + * @param array $migrations + * @param integer $version + */ + protected function _assertUniqueMigrationVersion($migrations, $version) + { + if (isset($migrations[$version])) { + $msg = "Multiple migrations have the version number $version"; + throw new Horde_Db_Migration_Exception($msg); + } + } + + /** + * Get the list of migration files + * @return array + */ + protected function _getMigrationFiles() + { + $files = glob("$this->_migrationsPath/[0-9]*_*.php"); + return $this->_isDown() ? array_reverse($files) : $files; + } + + /** + * Actually return object, and not class + * + * @param string $migrationName + * @param int $version + * @return Horde_Db_Migration_Base + */ + protected function _getMigrationClass($migrationName, $version) + { + $className = Horde_Support_Inflector::camelize($migrationName); + return new $className(array( + 'connection' => $this->_connection, + 'version' => $version, + )); + } + + /** + * @param string $migrationFile + * @return array ($version, $name) + */ + protected function _getMigrationVersionAndName($migrationFile) + { + preg_match_all('/([0-9]+)_([_a-z0-9]*).php/', $migrationFile, $matches); + return array($matches[1][0], $matches[2][0]); + } + + /** + * @param integer $version + */ + protected function _setSchemaVersion($version) + { + $version = $this->_isDown() ? $version - 1 : $version; + $sql = "UPDATE schema_info SET version = " . (int)$version; + $this->_connection->update($sql); + } + + /** + * @return boolean + */ + protected function _isUp() + { + return $this->_direction == 'up'; + } + + /** + * @return boolean + */ + protected function _isDown() + { + return $this->_direction == 'down'; + } + + /** + * @return boolean + */ + protected function _hasReachedTargetVersion($version) + { + if ($this->_targetVersion === null) { return false; } + + return ($this->_isUp() && $version-1 >= $this->_targetVersion) || + ($this->_isDown() && $version <= $this->_targetVersion); + } + + /** + * @param integer $version + * @return boolean + */ + protected function _isIrrelevantMigration($version) + { + return ($this->_isUp() && $version <= self::getCurrentVersion()) || + ($this->_isDown() && $version > self::getCurrentVersion()); + } + +}