Last active
December 25, 2015 02:29
-
-
Save mindplay-dk/6902895 to your computer and use it in GitHub Desktop.
Simple (forward migration) command-line SQL script runner for MySQL.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Simple (forward migration) command-line SQL script runner for MySQL. | |
* | |
* To initialize the ".upgrade" file: | |
* | |
* php upgrade.php --init | |
* | |
* To flag all SQL files as applied (in the status file) without actually doing any work: | |
* | |
* php upgrade.php --dry | |
* | |
* To run migrations from "./updates/*.sql": | |
* | |
* php upgrade.php --user=foo --password=bar --database=mydb | |
* | |
* To run migrations from another folder: | |
* | |
* php upgrade.php --user=foo --password=bar --database=mydb --mask=../foo/bar/*.sql | |
* | |
* List of switches: | |
* | |
* --database={name} database name - required | |
* --host={hostname} database hostname - default is "localhost" | |
* --user={username} database username - default is "root" | |
* --password={password} database password - default is empty | |
* --status={path} path to ".upgrade" status file - defaults to "./.upgrade" | |
* --mask={wildcard} path and wildcard of SQL files to apply - defaults to "./updates/*.sql" | |
* --dry flag SQL files as applied (in the status file) but do not actually apply them | |
* --init initialize (or rewrite) the status file | |
*/ | |
class UpgradeTask | |
{ | |
public $init; | |
public $user; | |
public $password; | |
public $database; | |
public $status; | |
public $mask; | |
public $dry = false; | |
public function __construct() | |
{ | |
$this->host = 'localhost'; | |
$this->status = __DIR__ . DIRECTORY_SEPARATOR . '.upgrade'; | |
$this->mask = __DIR__ . DIRECTORY_SEPARATOR . 'updates' . DIRECTORY_SEPARATOR . '*.sql'; | |
} | |
/** | |
* @param string $path SQL file path | |
* | |
* @return bool true on success, false on failure | |
*/ | |
private function runSQL($path) | |
{ | |
$command = 'mysql' | |
. ' --host=' . $this->host | |
. ' --user=' . $this->user | |
. ($this->password ? ' --password=' . $this->password : '') | |
. ' --database=' . $this->database | |
. ' --execute="SOURCE ' . $path . '"'; | |
passthru($command, $status); | |
return $status === 0; | |
} | |
/** | |
* Initialize (create or rewrite) the ".upgrade" status file | |
* | |
* @return int error level | |
*/ | |
public function init() | |
{ | |
file_put_contents($this->status, ''); | |
echo "Initialized status file: {$this->status}"; | |
return 0; | |
} | |
/** | |
* @return int error level | |
*/ | |
public function run() | |
{ | |
/** | |
* @var string $dot_path path to the "runtime/.upgrade" file used to track the last deployed updates | |
*/ | |
if (! file_exists($this->status)) { | |
echo "Please run 'upgrade init' to initialize the 'runtime/.upgrade' status file.\n"; | |
return 1; | |
} | |
if (! isset($this->user, $this->database)) { | |
echo "Must specify the username, password and database using command-line switches.\n"; | |
return 1; | |
} | |
$sql_path = dirname($this->mask); | |
$sql_files = glob($this->mask); | |
echo count($sql_files) . " files found in: {$sql_path}\n"; | |
$applied = array_flip(array_filter(explode("\n", file_get_contents($this->status)))); | |
if (count($sql_files) === 0) { | |
echo "No files found...\n"; | |
return 0; | |
} | |
$num = 0; | |
$total = count($sql_files) - count($applied); | |
if ($total === 0) { | |
echo "No pending upgrades...\n"; | |
return 0; | |
} | |
echo "{$total} upgrade(s) pending\n"; | |
if ($this->dry) { | |
echo "*** DRY RUN: SQL files will not be applied.\n"; | |
} | |
foreach ($sql_files as $sql_file) { | |
$filename = basename($sql_file); | |
if (isset($applied[$filename])) { | |
continue; // file already applied | |
} | |
$num += 1; | |
echo "Applying upgrade: {$filename} ({$num} of {$total})\n"; | |
// load and attempt to execute: | |
if (! $this->dry) { | |
if (! $this->runSQL($sql_file)) { | |
echo "*** Aborting upgrades due to error!\n\n"; | |
return 10; | |
} | |
} | |
// flag file as applied: | |
$applied[$filename] = true; | |
file_put_contents($this->status, implode("\n", array_keys($applied))); | |
} | |
return 0; | |
} | |
} | |
$task = new UpgradeTask(); | |
$options = getopt('', array('init', 'dry', 'host:', 'user:', 'password:', 'database:', 'status:', 'mask:')); | |
if (array_key_exists('dry', $options)) { | |
unset($options['dry']); | |
$task->dry = true; | |
} | |
foreach ($options as $name => $value) { | |
$task->$name = $value; | |
} | |
if (array_key_exists('init', $options)) { | |
exit($task->init()); | |
} | |
exit($task->run()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment