├── .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 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 |
--------------------------------------------------------------------------------