Advertisement
Guest User

compare whole database

a guest
Sep 26th, 2011
119
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 11.05 KB | None | 0 0
  1. <?php
  2.  
  3. // This file is part of Moodle - http://moodle.org/
  4. //
  5. // Moodle is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // Moodle is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. // GNU General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU General Public License
  16. // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17.  
  18. /**
  19.  * CLI DB comparison utility useful for automated CI jobs
  20.  *
  21.  * This script compares (using own Moodle DB schema facilitites) two DBs
  22.  * reporting any difference found between them. Useful to build some CI jobs
  23.  * on top of it.
  24.  *
  25.  * TODO: Some day, allow to specify different library/type for comparison,
  26.  *       simply has not been implemented for now because it's ok for our CI purposes
  27.  *
  28.  * @package    core
  29.  * @subpackage ci
  30.  * @copyright  2011 Eloy Lafuente (http://stronk7.com)
  31.  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32.  */
  33.  
  34. define('CLI_SCRIPT', true);
  35. define('NO_OUTPUT_BUFFERING', true);
  36.  
  37. require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
  38. require_once($CFG->libdir.'/clilib.php');      // cli only functions
  39.  
  40. // now get cli options
  41. list($options, $unrecognized) = cli_get_params(array(
  42.                                                    'help'   => false,
  43.                                                    'dblibrary' => 'native',
  44.                                                    'dbtype'  => 'mysqli',
  45.                                                    'dbhost1'   => 'localhost',
  46.                                                    'dbhost2'   => '',
  47.                                                    'dbuser1'   => '',
  48.                                                    'dbuser2'   => '',
  49.                                                    'dbpass1'   => '',
  50.                                                    'dbpass2'   => '',
  51.                                                    'dbname1'   => '',
  52.                                                    'dbname2'   => '',
  53.                                                    'dbprefix1' => 'mdl_',
  54.                                                    'dbprefix2' => ''),
  55.                                                array(
  56.                                                    'h' => 'help'));
  57.  
  58. if ($unrecognized) {
  59.     $unrecognized = implode("\n  ", $unrecognized);
  60.     cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
  61. }
  62.  
  63. if (empty($options['dblibrary']) || empty($options['dbtype']) || empty($options['dbhost1']) ||
  64.     empty($options['dbuser1']) || empty($options['dbpass1']) || empty($options['dbname1']) ||
  65.     empty($options['dbprefix1'])) {
  66.  
  67.     cli_error('Missing dblibrary/dbtype/dbhost1/dbuser1/dbpass1/dbname1/dbprefix1 param. Please use --help option.');
  68. }
  69.  
  70. $options['dbhost2'] = empty($options['dbhost2']) ? $options['dbhost1'] : $options['dbhost2'];
  71. $options['dbuser2'] = empty($options['dbuser2']) ? $options['dbuser1'] : $options['dbuser2'];
  72. $options['dbpass2'] = empty($options['dbpass2']) ? $options['dbpass1'] : $options['dbpass2'];
  73. $options['dbname2'] = empty($options['dbname2']) ? $options['dbname1'] : $options['dbname2'];
  74. $options['dbprefix2'] = empty($options['dbprefix2']) ? $options['dbprefix1'] : $options['dbprefix2'];
  75.  
  76. if ($options['help']) {
  77.     $help =
  78. "Compare 2 database schemas, using built-in Moodle facilities
  79.  
  80. Options:
  81. -h, --help            Print out this help
  82. --
  83. --dblibrary           Type of PHP driver used (native, pdo..). Defaults to native
  84. --dbtype              Name of the driver used (mysqli, pqsql...). Defaults to mysqli
  85. --dbhostX             IP/Name of the host. Defaults to localhost, 2nd defaults to 1st
  86. --dbuserX             Login to the database. 2nd defaults to 1st
  87. --dbpassX             Password to the database. 2nd defaults to 1st
  88. --dbnameX             Name of the database. 2nd defaults to 1st
  89. --dbprefixX           Prefix to apply to all DB objects. Defaults to mdl. 2nd defaults to 1st
  90.  
  91. Example:
  92. \$sudo -u www-data /usr/bin/php admin/ci/compare_databases/compare_databases.php --dbuser1=stronk7 --dbpass1=mojitos --dbname1=dbone --dbname2=dbtwo
  93. ";
  94.  
  95.     echo $help;
  96.     die;
  97. }
  98.  
  99. // Always run the comparison in developer debug mode.
  100. $CFG->debug = DEBUG_DEVELOPER;
  101. error_reporting($CFG->debug);
  102. raise_memory_limit(MEMORY_EXTRA);
  103.  
  104. // Let's connect to both ends
  105. $db1 = compare_connect($options['dblibrary'], $options['dbtype'], $options['dbhost1'], $options['dbuser1'],
  106.                        $options['dbpass1'], $options['dbname1'], $options['dbprefix1']);
  107. $db2 = compare_connect($options['dblibrary'], $options['dbtype'], $options['dbhost2'], $options['dbuser2'],
  108.                        $options['dbpass2'], $options['dbname2'], $options['dbprefix2']);
  109.  
  110. list($tablesarr, $errorsarr) = compare_tables($db1, $db2);
  111.  
  112. foreach ($tablesarr as $tname => $tinfo) {
  113.     $errorsarr = array_merge($errorsarr, compare_columns($tname, $tinfo->columns1, $tinfo->columns2));
  114.     $errorsarr = array_merge($errorsarr, compare_indexes($tname, $tinfo->indexes1, $tinfo->indexes2));
  115. }
  116.  
  117. // Errors found, print them
  118. $nerrors = count($errorsarr);
  119. if ($errorsarr) {
  120.     // Prepare params
  121.     ksort($options);
  122.     $paramstxt = '  Parameters: ';
  123.     foreach ($options as $key => $value) {
  124.         if ($key == 'dbpass1' || $key == 'dbpass2' || $key == 'help') {
  125.             continue;
  126.         }
  127.         $paramstxt .= "{$key}={$value}, ";
  128.     }
  129.     $paramstxt = substr($paramstxt, 0, -2);
  130.     echo "Problems found comparing databases!" . PHP_EOL;
  131.     echo $paramstxt . PHP_EOL;
  132.     echo "  Number of errors: {$nerrors}" . PHP_EOL;
  133.     echo PHP_EOL;
  134.     foreach ($errorsarr as $error) {
  135.         echo "  {$error}" . PHP_EOL;
  136.     }
  137. }
  138. exit(empty($nerrors) ? 0 : 1);
  139.  
  140. // SOME useful functions go here
  141.  
  142. function compare_beautify($val) {
  143.     if ($val === null) {
  144.         return 'null';
  145.     }
  146.     if ($val === true) {
  147.         return 'true';
  148.     }
  149.     if ($val === false) {
  150.         return 'false';
  151.     }
  152.     return $val;
  153. }
  154.  
  155. function compare_column_specs($tname, $cname, $specs1, $specs2) {
  156.     $errors = array();
  157.  
  158.     // Take out all the elements in the specs not defined in both sides
  159.     foreach ($specs1 as $key => $value) {
  160.         if (!array_key_exists($key, $specs2)) {
  161.             unset($specs1[$key]);
  162.         }
  163.     }
  164.     foreach ($specs2 as $key => $value) {
  165.         if (!array_key_exists($key, $specs1)) {
  166.             unset($specs2[$key]);
  167.         }
  168.     }
  169.     // Now strict compare the existing specs
  170.     foreach ($specs1 as $key => $value) {
  171.         if ($specs1[$key] !== $specs2[$key]) {
  172.             $val1 = compare_beautify($specs1[$key]);
  173.             $val2 = compare_beautify($specs2[$key]);
  174.             $errors[] = "Column {$cname} of table {$tname} difference found in {$key}: {$val1} !== {$val2}";
  175.         }
  176.     }
  177.     return $errors;
  178. }
  179.  
  180. function compare_columns($tname, $info1, $info2) {
  181.     $errors = array();
  182.  
  183.     foreach ($info1 as $cname => $cvalue) {
  184.         if (!isset($info2[$cname])) {
  185.             $errors[] = "Column {$cname} of table {$tname} only available in first DB";
  186.             unset($info1[$cname]);
  187.         }
  188.     }
  189.  
  190.     foreach ($info2 as $cname => $cvalue) {
  191.         if (!isset($info1[$cname])) {
  192.             $errors[] = "Column {$cname} of table {$tname} only available in second DB";
  193.             unset($info2[$cname]);
  194.         }
  195.     }
  196.  
  197.     // For the remaining elements, compare specs
  198.     foreach ($info1 as $cname => $cvalue) {
  199.         $errors = array_merge($errors, compare_column_specs($tname, $cname, (array)$cvalue, (array)$info2[$cname]));
  200.     }
  201.     return $errors;
  202. }
  203.  
  204. function compare_indexes($tname, $info1, $info2) {
  205.     $ninfo1 = array();
  206.     $ninfo2 = array();
  207.     $errors = array();
  208.  
  209.     // Normalize info (we ignore index names)
  210.     foreach ($info1 as $iname => $ivalue) {
  211.         $ikey = implode('-', $ivalue['columns']);
  212.         $ninfo1[$ikey] = $ivalue;
  213.     }
  214.     foreach ($info2 as $iname => $ivalue) {
  215.         $ikey = implode('-', $ivalue['columns']);
  216.         $ninfo2[$ikey] = $ivalue;
  217.     }
  218.  
  219.     foreach ($ninfo1 as $iname => $ivalue) {
  220.         if (!isset($ninfo2[$iname])) {
  221.             $ikey = implode('-', $ivalue['columns']);
  222.             $errors[] = "Index ({$ikey}) of table {$tname} only available in first DB";
  223.             unset($ninfo1[$iname]);
  224.         }
  225.     }
  226.  
  227.     foreach ($ninfo2 as $iname => $ivalue) {
  228.         if (!isset($ninfo1[$iname])) {
  229.             $ikey = implode('-', $ivalue['columns']);
  230.             $errors[] = "Index ({$ikey}) of table {$tname} only available in second DB";
  231.             unset($ninfo2[$iname]);
  232.         }
  233.     }
  234.  
  235.     // For the remaining elements, compare specs (only unique needed)
  236.     foreach ($ninfo1 as $iname => $ivalue) {
  237.         if ($ivalue['unique'] !== $ninfo2[$iname]['unique']) {
  238.             $val1 = compare_beautify($ivalue['unique']);
  239.             $val2 = compare_beautify($ninfo2[$iname]['unique']);
  240.             $ikey = implode('-', $ivalue['columns']);
  241.             $errors[] = "Index ({$ikey}) of table {$tname} difference found in unique: {$val1} !== {$val2}";
  242.         }
  243.     }
  244.     return $errors;
  245. }
  246.  
  247. function compare_tables($db1, $db2) {
  248.     $tocompare = array();
  249.     $errors    = array();
  250.  
  251.     $tables1 = $db1->get_tables();
  252.     $tables2 = $db2->get_tables();
  253.  
  254.     foreach ($tables1 as $tname => $tvalue) {
  255.         if (isset($tables2[$tname])) {
  256.             $tocompare[$tname] = new stdClass();
  257.             unset($tables2[$tname]);
  258.         } else {
  259.             $errors[] = "Table {$tname} only available in first DB";
  260.         }
  261.         unset($tables1[$tname]);
  262.     }
  263.  
  264.     foreach ($tables2 as $tname => $tvalue) {
  265.         $errors[] = "Table {$tname} only available in second DB";
  266.         unset($tables2[$tname]);
  267.     }
  268.  
  269.     foreach ($tocompare as $tname => $element) {
  270.         $element->columns1 = $db1->get_columns($tname);
  271.         $element->indexes1 = $db1->get_indexes($tname);
  272.         $element->columns2 = $db2->get_columns($tname);
  273.         $element->indexes2 = $db2->get_indexes($tname);
  274.     }
  275.     return array($tocompare, $errors);
  276. }
  277.  
  278.  
  279. function compare_connect($library, $type, $host, $user, $pass, $name, $prefix) {
  280.     global $CFG;
  281.  
  282.     $classname = "{$type}_{$library}_moodle_database";
  283.     if (!file_exists("$CFG->libdir/dml/$classname.php")) {
  284.         cli_error("Error connecting to DB: Driver {$classname} not available");
  285.     }
  286.     require_once("$CFG->libdir/dml/$classname.php");
  287.     $DB = new $classname();
  288.     if (!$DB->driver_installed()) {
  289.         cli_error("Error connecting to DB: PHP driver for {$classname} not installed");
  290.     }
  291.     try {
  292.         $DB->connect($host, $user, $pass, $name, $prefix, array());
  293.     } catch (dml_connection_exception $e) {
  294.         cli_error("Error connecting to DB: Cannot connect to {$name}");
  295.     }
  296.     return $DB;
  297. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement