diff options
author | Joe Freeman <joe.freeman@bitroot.com> | 2014-10-29 10:23:44 +0100 |
---|---|---|
committer | Joe Freeman <joe.freeman@bitroot.com> | 2014-10-29 10:23:44 +0100 |
commit | 14d6a4960ae34905a7a952480e735fc103777a25 (patch) | |
tree | df74cdacd3c1119e31d67350db69d102695f2706 | |
parent | cb3637ae1301aa7b364b886ddf6e9ee3e54d1b69 (diff) | |
download | dbdiff-14d6a4960ae34905a7a952480e735fc103777a25.zip dbdiff-14d6a4960ae34905a7a952480e735fc103777a25.tar.gz dbdiff-14d6a4960ae34905a7a952480e735fc103777a25.tar.bz2 |
Initial import.
-rw-r--r-- | DbDiff.php | 146 | ||||
-rw-r--r-- | LICENSE | 201 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | config.php | 28 | ||||
-rw-r--r-- | index.php | 246 | ||||
-rw-r--r-- | style.css | 119 |
6 files changed, 741 insertions, 1 deletions
diff --git a/DbDiff.php b/DbDiff.php new file mode 100644 index 0000000..72e0d8e --- /dev/null +++ b/DbDiff.php @@ -0,0 +1,146 @@ +<?php + +/** + * Compare the schemas of between databases. + * + * For two database schemas to be considered the same, they must have the same + * tables, where each table has the same fields, and each field has the same + * parameters. + * + * Field parameters that are compared are those that are given by the MySQL + * 'SHOW COLUMNS' command. These are: the field's name, it's type, whether the + * field can store null values, whether the column is indexed, the default + * values and whether the field was created with the 'auto_increment' keyword. + */ +class DbDiff { + + /** + * Export the schema of the database into an array. + * + * @param string $config Config details for the database connection. + * @param string $name Name or description of the database. + * @return mixed|string An array structure of the exported schema, or an error string. + */ + function export($config, $name) { + + $db = @mysql_connect($config['host'], $config['user'], + $config['password']); + + if (!$db) { + return null; + } + + if (!mysql_select_db($config['name'], $db)) { + return null; + } + + $result = mysql_query("SHOW TABLES"); + while ($row = mysql_fetch_row($result)) { + $tables[$row[0]] = array(); + } + + foreach ($tables as $table_name => $fields) { + + $result = mysql_query("SHOW COLUMNS FROM " . $table_name, $db); + while ($row = mysql_fetch_assoc($result)) { + $tables[$table_name][$row['Field']] = $row; + } + } + + mysql_close(); + + $data = array( + 'name' => $name, + 'time' => time(), + 'tables' => $tables + ); + + return $data; + } + + /** + * Compare two schemas (as generated by the 'export' method.) + * + * @param string $schema1 The first database schema. + * @param string $schema2 The second database schema. + * @return array The results of the comparison. + */ + function compare($schema1, $schema2) { + + $tables1 = array_keys($schema1['tables']); + $tables2 = array_keys($schema2['tables']); + + $tables = array_unique(array_merge($tables1, $tables2)); + + $results = array(); + + foreach ($tables as $table_name) { + + // Check tables exist in both databases + + if (!isset($schema1['tables'][$table_name])) { + + $results[$table_name][] = '<em>' . $schema1['name'] + . '</em> is missing table: <code>' . $table_name + . '</code>.'; + + continue; + } + + if (!isset($schema2['tables'][$table_name])) { + + $results[$table_name][] = '<em>' . $schema2['name'] + . '</em> is missing table: <code>' . $table_name + . '</code>.'; + + continue; + } + + // Check fields exist in both tables + + $fields = array_merge($schema1['tables'][$table_name], + $schema2['tables'][$table_name]); + + foreach ($fields as $field_name => $field) { + + if (!isset($schema1['tables'][$table_name][$field_name])) { + + $results[$table_name][] = '<em>' . $schema1['name'] + . '</em> is missing field: <code>' . $field_name + . '</code>'; + + continue; + } + + if (!isset($schema2['tables'][$table_name][$field_name])) { + + $results[$table_name][] = '<em>' . $schema2['name'] + . '</em> is missing field: <code>' . $field_name + . '</code>'; + + continue; + } + + // Check that the specific parameters of the fields match + + $s1_params = $schema1['tables'][$table_name][$field_name]; + $s2_params = $schema2['tables'][$table_name][$field_name]; + + foreach ($s1_params as $name => $details) { + if ($s1_params[$name] != $s2_params[$name]) { + $results[$table_name][] = 'Field <code>' . $field_name + . '</code> differs between databases for parameter \'' + . $name . '\'. <em>' . $schema1['name'] + . '</em> has \'' . $s1_params[$name] + . '\' and <em>' . $schema2['name'] + . '</em> has \'' . $s2_params[$name] . '\'.'; + } + } + } + } + + return $results; + } +} + +?> @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. @@ -1,4 +1,4 @@ dbdiff ====== -PHP script to compare MySQL database schemas +A PHP script to compare MySQL database schemas. More information available on [the blog post](http://joef.co.uk/blog/2009/07/php-script-to-compare-mysql-database-schemas/). diff --git a/config.php b/config.php new file mode 100644 index 0000000..d5ff315 --- /dev/null +++ b/config.php @@ -0,0 +1,28 @@ +<?php + +/** + * This file should contain the configuration of databases. + * + * $dbs_config is an array of database configurations. Each element of the + * array should provide details for a database which will be selectable from + * a list. + * + * This is arguably more secure and convenient than submitting database + * details with an HTML form (and sending them over an unsecured channel). + * + * Refer to the 'Demo Configuration' below for reference. + */ + +$dbs_config = array( + // array( + // 'name' => 'Demo Configuration', + // 'config' => array( + // 'host' => 'localhost', + // 'user' => 'db_user', + // 'password' => 'db_password', + // 'name' => 'db_name' + // ) + // ), +); + +?>
\ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..6585922 --- /dev/null +++ b/index.php @@ -0,0 +1,246 @@ +<?php + +/** + * This provides a user-interface for using the DbDiff class. + */ + +error_reporting(E_ALL); + +require_once('DbDiff.php'); +require('config.php'); + + +/** + * Display options and instructions. + * + * @return void + */ +function show_options($dbs_config) { + + echo '<h3>Step 1: Export database schemas</h3>'; + + if (count($dbs_config) > 0) { + + echo '<p class="info">Select a database configuration from the list below, or select \'Enter details...\'</p>'; + + echo '<ul id="db-list">'; + foreach ($dbs_config as $key => $db_config) { + echo '<li><a href="?a=export_schema&db=' . $key . '">' . $db_config['name'] . '</a></li>'; + } + echo '<li><em><a href="#" onclick="document.getElementById(\'db-config\').style.display=\'block\';return false;">Enter details...</a></em></li>'; + echo '</ul>'; + + } else { + + echo '<p class="info">Enter connection details in the form below, or setup a database connection in the <code>config.php</code> file.</p>'; + } + + echo '<form action="?a=export_schema" method="post" id="db-config"' . (count($dbs_config) > 0 ? ' style="display:none;"' : '' ) . '>'; + echo '<div class="field"><label for="db-host">Host</label><input type="text" name="db-host" id="db-host" value="localhost" /></div>'; + echo '<div class="field"><label for="db-user">User</label><input type="text" name="db-user" id="db-user" /></div>'; + echo '<div class="field"><label for="db-password">Password</label><input type="password" name="db-password" id="db-password" /></div>'; + echo '<div class="field"><label for="db-name">Database</label><input type="text" name="db-name" id="db-name" /></div>'; + echo '<div class="submit"><input type="submit" value="Export" /></div><div class="clearer"></div>'; + echo '</form>'; + + echo '<h3>Step 2: Compare schemas</h3>'; + + echo '<p class="info">Once two database schemas have been exported, paste them here to be compared.</p>'; + + echo '<form action="?a=compare" method="post" id="compare">'; + echo '<div class="field"><label for="schema1">First schema</label><textarea name="schema1" id="schema1" cols="100" rows="5"></textarea></div>'; + echo '<div class="field"><label for="schema2">Second schema</label><textarea name="schema2" id="schema2" cols="100" rows="5"></textarea></div>'; + echo '<div class="submit"><input type="submit" value="Compare" /></div>'; + echo '</form>'; +} + +/** + * Convenience method for outputting errors. + * + * @return void + **/ +function echo_error($error) { + echo '<p class="error">', $error, '</p>'; +} + +/** + * Export the schema from the database specified and echo the results. + * + * @param string $db The key of the config to be extracted from $dbs_config. + * @return void + */ +function export_schema($config) { + + $result = DbDiff::export($config['config'], $config['name']); + + if ($result == null) { + echo_error('Couldn\'t connect to database: ' . mysql_error()); + return; + } + + $serialized_schema = serialize($result); + + echo '<h3>Exported \'' . $config['name'] . '\'</h3>'; + + echo '<p>Copy the following schema information and then proceed to <a href="?">step 2</a>.</p>'; + + echo '<textarea cols="100" rows="20" onclick="this.focus();this.select();">'; + echo chunk_split($serialized_schema, 100); + echo '</textarea>'; +} + +/** + * Strips new line characters (CR and LF) from a string. + * + * @param string $str The string to process. + * @return string The string without CRs or LFs. + */ +function strip_nl($str) { + return str_replace(array("\n", "\r"), '', $str); +} + +/** + * Returns an 's' character if the count is not 1. + * + * This is useful for adding plurals. + * + * @return string An 's' character or an empty string + **/ +function s($count) { + return $count != 1 ? 's' : ''; +} + +/** + * Compare the two schemas and echo the results. + * + * @param string $schema1 The first schema (serialized). + * @param string $schema2 The second schema (serialized). + * @return void + */ +function do_compare($schema1, $schema2) { + + if (empty($schema1) || empty($schema2)) { + echo_error('Both schemas must be given.'); + return; + } + + $unserialized_schema1 = unserialize(strip_nl($schema1)); + $unserialized_schema2 = unserialize(strip_nl($schema2)); + + $results = DbDiff::compare($unserialized_schema1, $unserialized_schema2); + + if (count($results) > 0) { + + echo '<h3>Found differences in ' . count($results) . ' table' . s(count($results)) . ':</h3>'; + + echo '<ul id="differences">'; + foreach ($results as $table_name => $differences) { + + echo '<li><strong>' . $table_name . '</strong><ul>'; + foreach ($differences as $difference) { + echo '<li>' . $difference . '</li>'; + } + echo '</ul></li>'; + } + echo '</ul>'; + + } else { + echo '<p>No differences found.</p>'; + } +} + +?> +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> + +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>DbDiff</title> + + <link rel="stylesheet" href="style.css" type="text/css" media="screen" /> + +</head> +<body> + +<div id="canvas"> + +<h1><a href="?">DbDiff</a></h1> +<h2>Tool for comparing database schemas.</h2> + +<?php + +$action = @$_GET['a']; + +switch ($action) { + + case 'export_schema': + + if (isset($_GET['db'])) { + + $db = $_GET['db']; + + if (!isset($dbs_config[$db])) { + echo_error('No database configuration selected.'); + break; + } + + $config = $dbs_config[$db]; + + } else { + + if (!isset($_POST['db-host']) || !isset($_POST['db-user']) + || !isset($_POST['db-password']) + || !isset($_POST['db-name'])) { + echo_error('No database configuration entered.'); + break; + } + + $config = array( + 'name' => $_POST['db-name'] . ' (' . $_POST['db-host'] . ')', + 'config' => array( + 'host' => $_POST['db-host'], + 'user' => $_POST['db-user'], + 'password' => $_POST['db-password'], + 'name' => $_POST['db-name'] + ) + ); + } + + export_schema($config); + + echo '<p><a href="?">« Back to main page</a></p>'; + + break; + + case 'compare': + + $schema1 = @$_POST['schema1']; + $schema2 = @$_POST['schema2']; + + if (get_magic_quotes_gpc()) { // sigh... + $schema1 = stripslashes($schema1); + $schema2 = stripslashes($schema2); + } + + do_compare($schema1, $schema2); + + echo '<p><a href="?">« Back to main page</a></p>'; + + break; + + default: + + show_options($dbs_config); +} + +?> + +<div id="footer"> + <p>More information on this tool is available from the corresponding <a href="http://joef.co.uk/blog/2009/07/php-script-to-compare-mysql-database-schemas/">blog post</a>.</p> +</div> + +</div> + +</body> +</html> diff --git a/style.css b/style.css new file mode 100644 index 0000000..8c4a866 --- /dev/null +++ b/style.css @@ -0,0 +1,119 @@ + +body { + background-color: #DDD; + font: 75% "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; +} + +h1 { + margin: 15px 0 0 0; +} + +h1 a { + color: #000; + text-decoration: none; +} + +h2 { + margin: 0 0 10px 0; + font-size: 10px; + padding-bottom: 10px; + border-bottom: 1px solid #444; +} + +textarea { + width: 100%; + font-family: courier, fixed; +} + +.info { + background-color: #EEF; + border-top: 1px solid #AAC; + border-bottom: 1px solid #AAC; + padding: 5px; +} + +.error { + background-color: #FEE; + border-top: 1px solid #C66; + border-bottom: 1px solid #C66; + padding: 5px; +} + +.clearer { + clear: both; +} + +form .field { + margin-bottom: 6px; +} + +form .field label { + vertical-align: top; + display: block; + font-size: 90%; +} + +form .submit { + clear: both; +} + +#canvas { + background-color: #FFF; + margin: 0 auto; + border: 1px solid #AAA; + width: 800px; + padding: 10px 20px; +} + +ul#db-list { + list-style-type: square; + margin-left: 15px; + padding-left: 15px; +} + +form#db-config { + border-top: 1px solid #DDD; + border-bottom: 1px solid #DDD; + padding: 10px; + background-color: #F5F5F5; +} + +form#db-config .field, form#db-config .submit { + float: left; +} + +form#db-config .field input { + width: 10em; + margin-right: 0.5em; +} + +form#compare .field { + width: 49%; + float: left; +} + +form#compare .field:first-child { + margin-right: 2%; +} + +ul#differences { + margin: 10px 15px; + padding: 0 15px; +} + +ul#differences ul { + margin: 5px 0 10px 15px; + padding: 0 0 0 15px; +} + +#footer { + margin: 20px 0 0; +} + +#footer, #footer a { + color: #999; +} + +#footer p { + margin: 5px 0; +} |