#!/usr/bin/php * * 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 ("domframework/console.php"); require_once ("domframework/dblayeroo.php"); require_once ("domframework/getopts.php"); require_once ("domframework/config.php"); /** Manage the configuration file */ class configuration extends \config { /** The constructor of the configuration set the differents params */ function __construct () { $this->default = array ( "db" => array ( "srcDSN" => "", "srcUser" => null, "srcPassword" => null, "dstDSN" => "", "dstUser" => null, "dstPassword" => null, ), "tables" => array ( ), "tablesRel" => array ( ), "fields" => array ( ), "tablesOrder" => array ( ), "deleteContent" => null, ); } } /** The main class */ class main { /** The constructor of the class do the main job */ public function __construct () { // Manage the Getopts $getopts = new getopts (); $getopts->add ("Help", "?h", array ("help"), "Help of the software"); $getopts->add ("Step", "", array ("step:"), "Restart at provided step", "StepIdentifier"); $getopts->add ("Debug", "", array ("debug"), "Display the SQL requests"); $getopts->add ("Execute", "", array ("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 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 = array ( "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 dblayeroo ($db["srcDSN"], $db["srcUser"], $db["srcPassword"]); $srcTables = $srcDB->listTables (); $dstDB = new dblayeroo ($db["dstDSN"], $db["dstUser"], $db["dstPassword"]); $dstTables = $dstDB->listTables (); $value = array ( "srcTables" => $srcTables, "dstTables" => $dstTables, ); $config->set ("tables", $value); $step++; echo "\n"; } echo "#### Connect to the defined databases\n"; $srcDB = new dblayeroo ($db["srcDSN"], $db["srcUser"], $db["srcPassword"]); $dstDB = new 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 = array (); 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 = array (); 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] = array (); 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 = array (); 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, array ("")))). "\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 (array ())->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 * @return string The answer */ public function ask ($question, $proposal = "") { $console = new 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); }