├── .gitignore ├── README.md └── application └── controllers └── compare.php /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP compare databases 2 | ===================== 3 | 4 | A Codeigniter Controller to determine if 2 mySQL Databases such as a 'Development' and a 'Live' database have the same tables and table structures. 5 | 6 | It can determine in a very basic way if there are tables of the same name on the Development and Live database that have differences in their structure and need to be updated. 7 | 8 | It will show the SQL commands needed to create/drop tables in the Live database if required. 9 | 10 | It will show the SQL commands needed to create/modify/drop fields in the Live database if required. 11 | 12 | It does not make any changes to the Live database, instead it lists the SQL commands so a user can decide to run them or not. -------------------------------------------------------------------------------- /application/controllers/compare.php: -------------------------------------------------------------------------------- 1 | CHARACTER_SET = "utf8 COLLATE utf8_general_ci"; 13 | $this->DB1 = $this->load->database('development', TRUE); // load the source/development database 14 | $this->DB2 = $this->load->database('live', TRUE); // load the destination/live database 15 | } 16 | 17 | function index() 18 | { 19 | /* 20 | * This will become a list of SQL Commands to run on the Live database to bring it up to date 21 | */ 22 | $sql_commands_to_run = array(); 23 | 24 | /* 25 | * list the tables from both databases 26 | */ 27 | $development_tables = $this->DB1->list_tables(); 28 | $live_tables = $this->DB2->list_tables(); 29 | 30 | /* 31 | * list any tables that need to be created or dropped 32 | */ 33 | $tables_to_create = array_diff($development_tables, $live_tables); 34 | $tables_to_drop = array_diff($live_tables, $development_tables); 35 | 36 | /** 37 | * Create/Drop any tables that are not in the Live database 38 | */ 39 | $sql_commands_to_run = (is_array($tables_to_create) && !empty($tables_to_create)) ? array_merge($sql_commands_to_run, $this->manage_tables($tables_to_create, 'create')) : array(); 40 | $sql_commands_to_run = (is_array($tables_to_drop) && !empty($tables_to_drop)) ? array_merge($sql_commands_to_run, $this->manage_tables($tables_to_drop, 'drop')) : array(); 41 | 42 | $tables_to_update = $this->compare_table_structures($development_tables, $live_tables); 43 | 44 | /* 45 | * Before comparing tables, remove any tables from the list that will be created in the $tables_to_create array 46 | */ 47 | $tables_to_update = array_diff($tables_to_update, $tables_to_create); 48 | 49 | /* 50 | * update tables, add/update/emove columns 51 | */ 52 | $sql_commands_to_run = (is_array($tables_to_update) && !empty($tables_to_update)) ? array_merge($sql_commands_to_run, $this->update_existing_tables($tables_to_update)) : ''; 53 | 54 | if (is_array($sql_commands_to_run) && !empty($sql_commands_to_run)) 55 | { 56 | echo "

The database is out of Sync!

\n"; 57 | echo "

The following SQL commands need to be executed to bring the Live database tables up to date:

\n"; 58 | echo "
\n";
 59 |             foreach ($sql_commands_to_run as $sql_command)
 60 |             {
 61 |                 echo "$sql_command\n";
 62 |             }
 63 |             echo "
\n";
 64 |         }
 65 |         else
 66 |         {
 67 |             echo "

The database appears to be up to date

\n"; 68 | } 69 | } 70 | 71 | /** 72 | * Manage tables, create or drop them 73 | * @param array $tables 74 | * @param string $action 75 | * @return array $sql_commands_to_run 76 | */ 77 | function manage_tables($tables, $action) 78 | { 79 | $sql_commands_to_run = array(); 80 | 81 | if ($action == 'create') 82 | { 83 | foreach ($tables as $table) 84 | { 85 | $query = $this->DB1->query("SHOW CREATE TABLE `$table` -- create tables"); 86 | $table_structure = $query->row_array(); 87 | $sql_commands_to_run[] = $table_structure["Create Table"] . ";"; 88 | } 89 | } 90 | 91 | if ($action == 'drop') 92 | { 93 | foreach ($tables as $table) 94 | { 95 | $sql_commands_to_run[] = "DROP TABLE $table;"; 96 | } 97 | } 98 | 99 | return $sql_commands_to_run; 100 | } 101 | 102 | /** 103 | * Go through each table, compare their sql structure 104 | * @param array $development_tables 105 | * @param array $live_tables 106 | */ 107 | function compare_table_structures($development_tables, $live_tables) 108 | { 109 | $tables_need_updating = array(); 110 | 111 | $live_table_structures = $development_table_structures = array(); 112 | 113 | /* 114 | * generate the sql for each table in the development database 115 | */ 116 | foreach ($development_tables as $table) 117 | { 118 | $query = $this->DB1->query("SHOW CREATE TABLE `$table` -- dev"); 119 | $table_structure = $query->row_array(); 120 | $development_table_structures[$table] = $table_structure["Create Table"]; 121 | } 122 | 123 | /* 124 | * generate the sql for each table in the live database 125 | */ 126 | foreach ($live_tables as $table) 127 | { 128 | $query = $this->DB2->query("SHOW CREATE TABLE `$table` -- live"); 129 | $table_structure = $query->row_array(); 130 | $live_table_structures[$table] = $table_structure["Create Table"]; 131 | } 132 | 133 | /* 134 | * compare the development sql to the live sql 135 | */ 136 | foreach ($development_tables as $table) 137 | { 138 | $development_table = $development_table_structures[$table]; 139 | $live_table = (isset($live_table_structures[$table])) ? $live_table_structures[$table] : ''; 140 | 141 | if ($this->count_differences($development_table, $live_table) > 0) 142 | { 143 | $tables_need_updating[] = $table; 144 | } 145 | } 146 | 147 | return $tables_need_updating; 148 | } 149 | 150 | /** 151 | * Count differences in 2 sql statements 152 | * @param string $old 153 | * @param string $new 154 | * @return int $differences 155 | */ 156 | function count_differences($old, $new) 157 | { 158 | $differences = 0; 159 | $old = trim(preg_replace('/\s+/', '', $old)); 160 | $new = trim(preg_replace('/\s+/', '', $new)); 161 | 162 | if ($old == $new) 163 | { 164 | return $differences; 165 | } 166 | 167 | $old = explode(" ", $old); 168 | $new = explode(" ", $new); 169 | $length = max(count($old), count($new)); 170 | 171 | for ($i = 0; $i < $length; $i++) 172 | { 173 | if ($old[$i] != $new[$i]) 174 | { 175 | $differences++; 176 | } 177 | } 178 | 179 | return $differences; 180 | } 181 | 182 | /** 183 | * Given an array of tables that differ from DB1 to DB2, update DB2 184 | * @param array $tables 185 | */ 186 | function update_existing_tables($tables) 187 | { 188 | $sql_commands_to_run = array(); 189 | $table_structure_development = array(); 190 | $table_structure_live = array(); 191 | 192 | if (is_array($tables) && !empty($tables)) 193 | { 194 | foreach ($tables as $table) 195 | { 196 | $table_structure_development[$table] = $this->table_field_data((array) $this->DB1, $table); 197 | $table_structure_live[$table] = $this->table_field_data((array) $this->DB2, $table); 198 | } 199 | } 200 | 201 | /* 202 | * add, remove or update any fields in $table_structure_live 203 | */ 204 | $sql_commands_to_run = array_merge($sql_commands_to_run, $this->determine_field_changes($table_structure_development, $table_structure_live)); 205 | 206 | return $sql_commands_to_run; 207 | } 208 | 209 | /** 210 | * Given a database and a table, compile an array of field meta data 211 | * @param array $database 212 | * @param string $table 213 | * @return array $fields 214 | */ 215 | function table_field_data($database, $table) 216 | { 217 | $conn = mysqli_connect($database["hostname"], $database["username"], $database["password"]); 218 | 219 | mysql_select_db($database["database"]); 220 | 221 | $result = mysql_query("SHOW COLUMNS FROM `$table`"); 222 | 223 | while ($row = mysql_fetch_assoc($result)) 224 | { 225 | $fields[] = $row; 226 | } 227 | 228 | return $fields; 229 | } 230 | 231 | /** 232 | * Given to arrays of table fields, add/edit/remove fields 233 | * @param type $source_field_structures 234 | * @param type $destination_field_structures 235 | */ 236 | function determine_field_changes($source_field_structures, $destination_field_structures) 237 | { 238 | $sql_commands_to_run = array(); 239 | 240 | /** 241 | * loop through the source (usually development) database 242 | */ 243 | foreach ($source_field_structures as $table => $fields) 244 | { 245 | foreach ($fields as $field) 246 | { 247 | if ($this->in_array_recursive($field["Field"], $destination_field_structures[$table])) 248 | { 249 | $modify_field = ''; 250 | /* 251 | * Check for required modifications 252 | */ 253 | for ($n = 0; $n < count($fields); $n++) 254 | { 255 | if (isset($fields[$n]) && isset($destination_field_structures[$table][$n]) && ($fields[$n]["Field"] == $destination_field_structures[$table][$n]["Field"])) 256 | { 257 | $differences = array_diff($fields[$n], $destination_field_structures[$table][$n]); 258 | 259 | if (is_array($differences) && !empty($differences)) 260 | { 261 | // ALTER TABLE `bugs` MODIFY COLUMN `site_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL AFTER `type`; 262 | // ALTER TABLE `bugs` MODIFY COLUMN `message` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL AFTER `site_name`; 263 | $modify_field = "ALTER TABLE $table MODIFY COLUMN `" . $fields[$n]["Field"] . "` " . $fields[$n]["Type"] . ' CHARACTER SET ' . $this->CHARACTER_SET; 264 | $modify_field .= (isset($fields[$n]["Default"]) && $fields[$n]["Default"] != '') ? ' DEFAULT \'' . $fields[$n]["Default"] . '\'' : ''; 265 | $modify_field .= (isset($fields[$n]["Null"]) && $fields[$n]["Null"] == 'YES') ? ' NULL' : ' NOT NULL'; 266 | $modify_field .= (isset($fields[$n]["Extra"]) && $fields[$n]["Extra"] != '') ? ' ' . $fields[$n]["Extra"] : ''; 267 | $modify_field .= (isset($previous_field) && $previous_field != '') ? ' AFTER ' . $previous_field : ''; 268 | $modify_field .= ';'; 269 | } 270 | $previous_field = $fields[$n]["Field"]; 271 | } 272 | 273 | if ($modify_field != '' && !in_array($modify_field, $sql_commands_to_run)) 274 | $sql_commands_to_run[] = $modify_field; 275 | } 276 | } 277 | else 278 | { 279 | /* 280 | * Add 281 | */ 282 | $add_field = "ALTER TABLE $table ADD COLUMN `" . $field["Field"] . "` " . $field["Type"] . " CHARACTER SET " . $this->CHARACTER_SET; 283 | $add_field .= (isset($field["Null"]) && $field["Null"] == 'YES') ? ' Null' : ''; 284 | $add_field .= " DEFAULT " . $field["Default"]; 285 | $add_field .= (isset($field["Extra"]) && $field["Extra"] != '') ? ' ' . $field["Extra"] : ''; 286 | $add_field .= ';'; 287 | $sql_commands_to_run[] = $add_field; 288 | } 289 | } 290 | } 291 | 292 | return $sql_commands_to_run; 293 | } 294 | 295 | /** 296 | * Recursive version of in_array 297 | * @param type $needle 298 | * @param type $haystack 299 | * @param type $strict 300 | * @return boolean 301 | */ 302 | function in_array_recursive($needle, $haystack, $strict = false) 303 | { 304 | foreach ($haystack as $array => $item) 305 | { 306 | $item = $item["Field"]; // look in the name field only 307 | if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && in_array_recursive($needle, $item, $strict))) 308 | { 309 | return true; 310 | } 311 | } 312 | 313 | return false; 314 | } 315 | 316 | } 317 | 318 | /* End of file compare.php */ 319 | /* Location: ./application/controllers/compare.php */ 320 | --------------------------------------------------------------------------------