408 lines
14 KiB
PHP
Executable File
408 lines
14 KiB
PHP
Executable File
#!/usr/bin/php
|
|
<?php
|
|
|
|
/**
|
|
* DomFramework
|
|
* @package domframework
|
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
|
*
|
|
* SQL Migrate
|
|
* This tool allow to migrate the data from one SQL database to another.
|
|
* The DB engine can also be different.
|
|
*
|
|
* You must have the source DB and create a target DB with all the destination
|
|
* tables available.
|
|
* You must pass one parameter : the configuration file where all the
|
|
* informations will be stored. The file will be created if it doesn't exists.
|
|
*/
|
|
|
|
require_once(__DIR__ . "/../src/Console.php");
|
|
require_once(__DIR__ . "/../src/Dblayeroo.php");
|
|
require_once(__DIR__ . "/../src/Getopts.php");
|
|
require_once(__DIR__ . "/../src/Config.php");
|
|
|
|
/**
|
|
* Manage the configuration file
|
|
*/
|
|
class Configuration extends \Domframework\Config
|
|
{
|
|
/**
|
|
* The constructor of the configuration set the differents params
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->default = [
|
|
"db" => [
|
|
"srcDSN" => "",
|
|
"srcUser" => null,
|
|
"srcPassword" => null,
|
|
"dstDSN" => "",
|
|
"dstUser" => null,
|
|
"dstPassword" => null,
|
|
],
|
|
"tables" => [
|
|
],
|
|
"tablesRel" => [
|
|
],
|
|
"fields" => [
|
|
],
|
|
"tablesOrder" => [
|
|
],
|
|
"deleteContent" => null,
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The main class
|
|
*/
|
|
class Main
|
|
{
|
|
/**
|
|
* The constructor of the class do the main job
|
|
*/
|
|
public function __construct()
|
|
{
|
|
// Manage the Getopts
|
|
$getopts = new Domframework\Getopts();
|
|
$getopts->add("Help", "?h", ["help"], "Help of the software");
|
|
$getopts->add(
|
|
"Step",
|
|
"",
|
|
["step:"],
|
|
"Restart at provided step",
|
|
"StepIdentifier"
|
|
);
|
|
$getopts->add("Debug", "", ["debug"], "Display the SQL requests");
|
|
$getopts->add(
|
|
"Execute",
|
|
"",
|
|
["execute"],
|
|
"Execute the configuration without requesting data (start at step 5)"
|
|
);
|
|
if ($getopts->get("Help")) {
|
|
die($getopts->help());
|
|
}
|
|
if ($getopts->get("Step")) {
|
|
if (
|
|
intval($getopts->get("Step")) < 0 ||
|
|
intval($getopts->get("Step")) > 6
|
|
) {
|
|
die("Invalid Step provided (Must be between 0 and 6)\n");
|
|
}
|
|
}
|
|
if (empty($getopts->restOfLine())) {
|
|
throw new \Exception("No configuration file provided");
|
|
}
|
|
if (count($getopts->restOfLine()) > 1) {
|
|
throw new \Exception("Too much configuration file provided");
|
|
}
|
|
$configurationFile = current($getopts->restOfLine());
|
|
if (! file_exists($configurationFile)) {
|
|
touch($configurationFile);
|
|
}
|
|
$config = new Domframework\Configuration();
|
|
$config->confFile = $configurationFile;
|
|
|
|
$db = $config->get("db");
|
|
$tables = $config->get("tables");
|
|
$tablesRel = $config->get("tablesRel");
|
|
$fields = $config->get("fields");
|
|
$tablesOrder = $config->get("tablesOrder");
|
|
$deleteContent = $config->get("deleteContent");
|
|
if ($db["srcDSN"] === "" || $db["dstDSN"] === "") {
|
|
$step = 0;
|
|
} elseif (empty($tables)) {
|
|
$step = 1;
|
|
} elseif (empty($tablesRel)) {
|
|
$step = 2;
|
|
} elseif (empty($fields)) {
|
|
$step = 3;
|
|
} elseif (empty($tablesOrder)) {
|
|
$step = 4;
|
|
} elseif ($deleteContent === null) {
|
|
$step = 5;
|
|
} else {
|
|
$step = 6;
|
|
}
|
|
|
|
if ($getopts->get("Step")) {
|
|
$step = intval($getopts->get("Step"));
|
|
}
|
|
if ($getopts->get("Execute")) {
|
|
$step = 5;
|
|
}
|
|
|
|
if ($step === 0) {
|
|
// Step 0: ask the connection parameters
|
|
echo "#### Entering step 0 : ask the connection parameters\n";
|
|
$srcDSN = $this->ask("Enter SRC DSN");
|
|
$srcUser = $this->ask("Enter SRC User");
|
|
$srcPassword = $this->ask("Enter SRC Password");
|
|
$dstDSN = $this->ask("Enter DST DSN");
|
|
$dstUser = $this->ask("Enter DST User");
|
|
$dstPassword = $this->ask("Enter DST Password");
|
|
if ($srcUser === "") {
|
|
$srcUser = null;
|
|
$srcPassword = null;
|
|
}
|
|
if ($dstUser === "") {
|
|
$dstUser = null;
|
|
$dstPassword = null;
|
|
}
|
|
$value = [
|
|
"srcDSN" => $srcDSN,
|
|
"srcUser" => $srcUser,
|
|
"srcPassword" => $srcPassword,
|
|
"dstDSN" => $dstDSN,
|
|
"dstUser" => $dstUser,
|
|
"dstPassword" => $dstPassword,];
|
|
$config->set("db", $value);
|
|
$db = $config->get("db");
|
|
$step++;
|
|
}
|
|
if ($step === 1) {
|
|
echo "#### Entering step 1: read the existing databases schemes\n";
|
|
$srcDB = new Domframework\Dblayeroo(
|
|
$db["srcDSN"],
|
|
$db["srcUser"],
|
|
$db["srcPassword"]
|
|
);
|
|
$srcTables = $srcDB->listTables();
|
|
$dstDB = new Domframework\Dblayeroo(
|
|
$db["dstDSN"],
|
|
$db["dstUser"],
|
|
$db["dstPassword"]
|
|
);
|
|
$dstTables = $dstDB->listTables();
|
|
$value = [
|
|
"srcTables" => $srcTables,
|
|
"dstTables" => $dstTables,
|
|
];
|
|
$config->set("tables", $value);
|
|
$step++;
|
|
echo "\n";
|
|
}
|
|
|
|
echo "#### Connect to the defined databases\n";
|
|
$srcDB = new Domframework\Dblayeroo(
|
|
$db["srcDSN"],
|
|
$db["srcUser"],
|
|
$db["srcPassword"]
|
|
);
|
|
$dstDB = new Domframework\Dblayeroo(
|
|
$db["dstDSN"],
|
|
$db["dstUser"],
|
|
$db["dstPassword"]
|
|
);
|
|
echo "\n";
|
|
|
|
if ($step == 2) {
|
|
echo "#### Entering step 2: ask the relations between tables\n";
|
|
$tables = $config->get("tables");
|
|
echo "# For each destination table, please provide the source table\n";
|
|
sort($tables["srcTables"]);
|
|
echo "# Allowed source tables : " . implode(", ", $tables["srcTables"]) .
|
|
"\n";
|
|
$value = [];
|
|
natsort($tables["dstTables"]);
|
|
foreach ($tables["dstTables"] as $table) {
|
|
while (1) {
|
|
$value[$table] = $this->ask(
|
|
"Destination Table '$table' filled by source table"
|
|
);
|
|
if (
|
|
in_array($value[$table], $tables["srcTables"]) ||
|
|
$value[$table] === ""
|
|
) {
|
|
continue 2;
|
|
}
|
|
$this->error("The table '" . $value[$table] . " doesn't exists");
|
|
echo "# Allowed source tables : " .
|
|
implode(", ", $tables["srcTables"]) . "\n";
|
|
}
|
|
}
|
|
$config->set("tablesRel", $value);
|
|
$step++;
|
|
echo "\n";
|
|
}
|
|
|
|
$tablesRel = $config->get("tablesRel");
|
|
if ($step === 3) {
|
|
echo "#### Entering step 3: ask the relation between fields\n";
|
|
echo "# For each destination table, if a source table is provided,\n" .
|
|
"# for each field, provide the source SQL request (can be field,\n" .
|
|
"# CONCAT(), text between simple quotes, null...)\n";
|
|
if ($config->get("fields")) {
|
|
$value = $config->get("fields");
|
|
} else {
|
|
$value = [];
|
|
}
|
|
foreach ($tablesRel as $dstTable => $srcTable) {
|
|
echo "- Destination Table '$dstTable': ";
|
|
if ($srcTable === "") {
|
|
echo "No source table. SKIPPED\n";
|
|
continue;
|
|
}
|
|
echo "\n";
|
|
$dstSchema = $dstDB->getTableSchema($dstTable);
|
|
$srcSchema = $srcDB->getTableSchema($srcTable);
|
|
echo " List of the allowed fields: " .
|
|
implode(", ", array_keys($srcSchema["fields"])) . "\n";
|
|
foreach (array_keys($dstSchema["fields"]) as $field) {
|
|
if (! key_exists($dstTable, $value)) {
|
|
$value[$dstTable] = [];
|
|
}
|
|
if (! key_exists($field, $value[$dstTable])) {
|
|
$value[$dstTable][$field] = "";
|
|
}
|
|
while (1) {
|
|
$oldval = (isset($config->get("fields")[$dstTable][$field])) ?
|
|
$config->get("fields")[$dstTable][$field] : "";
|
|
$answer = $this->ask(
|
|
"Destination Table '$dstTable' field '$field'",
|
|
$oldval
|
|
);
|
|
$value[$dstTable][$field] = $answer;
|
|
if (trim($value[$dstTable][$field]) !== "") {
|
|
break;
|
|
}
|
|
// TODO : Check if the field is valid (think about function)
|
|
$this->error("The field source must be defined !");
|
|
}
|
|
}
|
|
$config->set("fields", $value);
|
|
}
|
|
$step++;
|
|
echo "\n";
|
|
}
|
|
$fields = $config->get("fields");
|
|
if ($step === 4) {
|
|
echo "#### Entering step 4: Order the tables by foreign keys\n";
|
|
echo "# The tables with foreign keys constraint must be populated \n" .
|
|
"# after the tables depending of them\n";
|
|
$tablesToOrder = [];
|
|
foreach ($tablesRel as $dstTable => $srcTable) {
|
|
if ($srcTable !== "") {
|
|
$tablesToOrder[] = $dstTable;
|
|
}
|
|
}
|
|
while (1) {
|
|
echo "# Here are the tables to order: " . implode(",", $tablesToOrder) .
|
|
"\n";
|
|
$tablesOrder = $this->ask("Order the tables, separated by commas");
|
|
$tablesOrder = explode(",", $tablesOrder);
|
|
if (count($tablesToOrder) === count($tablesOrder)) {
|
|
break;
|
|
}
|
|
$this->error("Not all the tables are ordered");
|
|
}
|
|
$config->set("tablesOrder", $tablesOrder);
|
|
$step++;
|
|
echo "\n";
|
|
}
|
|
|
|
$tablesOrder = $config->get("tablesOrder");
|
|
if ($step === 5) {
|
|
if ($deleteContent === null) {
|
|
echo "# The content of the destination tables can be deleted before\n" .
|
|
"# adding the source data.\n";
|
|
echo "# Attention: the order is important if there is foreign keys!\n" .
|
|
"# Remove the most constraint tables before deleting the less\n" .
|
|
"# constraint ones\n";
|
|
echo "# List of the destination tables: " .
|
|
implode(",", array_keys(array_diff($tablesRel, [""]))) .
|
|
"\n";
|
|
$deleteContent = $this->ask("Get the list of the tables to be " .
|
|
"cleaned, separated by comas");
|
|
$config->set("deleteContent", $deleteContent);
|
|
$deleteContent = $config->get("deleteContent");
|
|
}
|
|
}
|
|
if ($deleteContent !== "") {
|
|
echo "#### Entering step 5: delete tables content\n";
|
|
foreach (explode(",", $deleteContent) as $table) {
|
|
$table = trim($table);
|
|
echo "- Delete content of table '$table'\n";
|
|
$dstDB->table($table)->unique([])->delete()->execute();
|
|
}
|
|
$step = 6;
|
|
echo "\n";
|
|
} else {
|
|
echo "#### Entering step5: Skipped delete tables content\n";
|
|
$step = 6;
|
|
}
|
|
if ($step === 6) {
|
|
echo "#### Entering step 6: populate the destination database with the " .
|
|
"configured data\n";
|
|
foreach ($tablesOrder as $dstTable) {
|
|
echo "- Populate table $dstTable:\n";
|
|
// Create the source SQL request. Add the alias name of the destination
|
|
// table to allow to import directely in the destination
|
|
$sql = "SELECT ";
|
|
$i = 0;
|
|
foreach ($fields[$dstTable] as $key => $val) {
|
|
if ($i > 0) {
|
|
$sql .= ",";
|
|
}
|
|
$sql .= "$val AS $key";
|
|
$i++;
|
|
}
|
|
$sql .= " FROM " . $tablesRel[$dstTable];
|
|
echo " $sql\n";
|
|
echo " ";
|
|
// Define all the variables needed to insert
|
|
$dstSchema = $dstDB->getTableSchema($dstTable);
|
|
$dstDB->table($dstTable);
|
|
$dstDB->fields($dstSchema["fields"]);
|
|
$dstDB->primary($dstSchema["primary"]);
|
|
foreach ($srcDB->directQuery($sql) as $row) {
|
|
//print_r ($row);
|
|
$dstDB->clearRequest();
|
|
$dstDB->insert()
|
|
->setValues($row);
|
|
if (! $getopts->get("Debug")) {
|
|
echo ".";
|
|
} else {
|
|
echo $dstDB->getDisplayQuery();
|
|
}
|
|
$dstDB->execute();
|
|
}
|
|
echo "\n";
|
|
}
|
|
$step++;
|
|
}
|
|
// Last step
|
|
echo "#### Entering step $step: End of process\n";
|
|
}
|
|
|
|
/**
|
|
* Ask a question to the user and return the answer
|
|
* @param string $question the question message
|
|
* @param string|null $proposal The optional pre-filled proposal
|
|
* @return string The answer
|
|
*/
|
|
public function ask($question, $proposal = "")
|
|
{
|
|
$console = new Domframework\Console();
|
|
$console->echo("$question: ");
|
|
return trim($console->readline($proposal));
|
|
}
|
|
|
|
/**
|
|
* Display an error to the user
|
|
* @param string $msg The error message to display
|
|
*/
|
|
public function error($msg)
|
|
{
|
|
file_put_contents("php://stderr", "ERROR: " . $msg . "\n");
|
|
}
|
|
}
|
|
|
|
try {
|
|
$main = new \main();
|
|
} catch (Exception $e) {
|
|
file_put_contents("php://stderr", $e->getMessage() . "\n");
|
|
exit(4);
|
|
}
|