Skip to content

Instantly share code, notes, and snippets.

@mindplay-dk
Last active December 25, 2015 02:29
Show Gist options
  • Save mindplay-dk/6902895 to your computer and use it in GitHub Desktop.
Save mindplay-dk/6902895 to your computer and use it in GitHub Desktop.
Simple (forward migration) command-line SQL script runner for MySQL.
<?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