PageRenderTime 57ms CodeModel.GetById 6ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/dbDiff.class.php

https://bitbucket.org/idler/mmp/
PHP | 387 lines | 321 code | 46 blank | 20 comment | 31 complexity | 28ebbb8aa31f23cc2d684967e37a3e78 MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0
  1. <?php
  2. class dbDiff
  3. {
  4. /**
  5. *
  6. * @var mysqli main database connection
  7. */
  8. protected $current;
  9. /**
  10. *
  11. * @var mysqli temp database connection
  12. */
  13. protected $published;
  14. /**
  15. *
  16. * @var array
  17. */
  18. protected $difference = array('up' => array(), 'down' => array());
  19. protected function up($sql)
  20. {
  21. if(!strlen($sql)) return;
  22. $this->difference['up'][] = $sql;
  23. }
  24. protected function down($sql)
  25. {
  26. if(!strlen($sql)) return;
  27. $this->difference['down'][] = $sql;
  28. }
  29. public function __construct($currentDbVersion, $lastPublishedDbVersion)
  30. {
  31. $this->current = $currentDbVersion;
  32. $this->published = $lastPublishedDbVersion;
  33. }
  34. public function getDifference()
  35. {
  36. $current_tables = $this->getTables($this->current);
  37. $published_tables = $this->getTables($this->published);
  38. $exclude_tables = Helper::get('exclude_tables');
  39. if(!empty($exclude_tables))
  40. {
  41. if($exclude_tables[0] != '/')
  42. {
  43. $exclude_tables = '/' . $exclude_tables . '/i';
  44. }
  45. foreach($current_tables as $k => $table)
  46. {
  47. if(preg_match($exclude_tables, $table))
  48. unset($current_tables[$k]);
  49. }
  50. foreach($published_tables as $k => $table)
  51. {
  52. if(preg_match($exclude_tables, $table))
  53. unset($published_tables[$k]);
  54. }
  55. }
  56. sort($current_tables);
  57. sort($published_tables);
  58. $this->createFullTableDifference($current_tables, $published_tables);
  59. $common = array_intersect($current_tables, $published_tables);
  60. $this->createDifferenceBetweenTables($common);
  61. return $this->difference;
  62. }
  63. protected function createFullTableDifference($current_tables, $published_tables)
  64. {
  65. sort($current_tables);
  66. sort($published_tables);
  67. $create = array_diff($current_tables, $published_tables);
  68. $drop = array_diff($published_tables, $current_tables);
  69. foreach ($create as $table) $this->addCreateTable($table, $this->current);
  70. foreach ($drop as $table) $this->addDropTable($table, $this->published);
  71. }
  72. protected function getTables($db)
  73. {
  74. $res = $db->query('show tables');
  75. $tables = array();
  76. while ($row = $res->fetch_array(MYSQLI_NUM))
  77. {
  78. $tables[] = $row[0];
  79. }
  80. return $tables;
  81. }
  82. protected function addCreateTable($tname, $db)
  83. {
  84. $this->down($this->dropTable($tname));
  85. $this->up($this->dropTable($tname));
  86. $this->up(Helper::getSqlForTableCreation($tname, $db));
  87. }
  88. protected function addDropTable($tname, $db)
  89. {
  90. $this->up($this->dropTable($tname));
  91. $this->down($this->dropTable($tname));
  92. $this->down(Helper::getSqlForTableCreation($tname, $db));
  93. }
  94. protected function createDifferenceBetweenTables($tables)
  95. {
  96. foreach ($tables as $table)
  97. {
  98. $query = "DESCRIBE `{$table}`";
  99. $table_current_columns = $this->getColumnList($this->current->query($query));
  100. $table_published_columns = $this->getColumnList($this->published->query($query));
  101. $this->createDifferenceInsideTable($table, $table_current_columns, $table_published_columns);
  102. $this->createIndexDifference($table);
  103. }
  104. }
  105. protected function getColumnList($result)
  106. {
  107. $columns = array();
  108. while ($row = $result->fetch_assoc())
  109. {
  110. unset($row['Key']);
  111. $columns[] = $row;
  112. }
  113. return $columns;
  114. }
  115. protected function createDifferenceInsideTable($table, $table_current_columns, $table_published_columns)
  116. {
  117. foreach ($table_current_columns as $current_column)
  118. {
  119. $column_for_compare = $this->checkColumnExists($current_column, $table_published_columns);
  120. if (!$column_for_compare)
  121. {
  122. $this->up($this->addColumn($table, $current_column));
  123. $this->down($this->dropColumn($table, $current_column));
  124. }
  125. else
  126. {
  127. if ($current_column === $column_for_compare) continue;
  128. $sql = $this->changeColumn($table, $current_column);
  129. $this->up($sql);
  130. $sql = $this->changeColumn($table, $column_for_compare);
  131. $this->down($sql);
  132. }
  133. }
  134. foreach ($table_published_columns as $published_column)
  135. {
  136. $has = $this->checkColumnExists($published_column, $table_current_columns);
  137. if (!$has)
  138. {
  139. $constraint = $this->getConstraintForColumn($this->published, $table, $published_column['Field']);
  140. //echo "COLUMNS\n\n"; var_dump($constraint);
  141. if(count($constraint))
  142. {
  143. $this->down($this->addConstraint(array('constraint'=>$constraint)));
  144. $this->up($this->dropConstraint(array('constraint'=>$constraint)));
  145. }
  146. $this->down($this->addColumn($table, $published_column));
  147. $this->up($this->dropColumn($table, $published_column));
  148. }
  149. }
  150. }
  151. protected function addSqlExtras( & $sql, $column)
  152. {
  153. if ($column['Null'] === 'NO') $sql .= " NOT NULL ";
  154. if (!is_null($column['Default'])) $sql .= " DEFAULT \\'{$column['Default']}\\' ";
  155. }
  156. protected function changeColumn($table, $column)
  157. {
  158. $sql = "ALTER TABLE `{$table}` CHANGE " .
  159. " `{$column['Field']}` `{$column['Field']}` " .
  160. addslashes($column['Type']);
  161. $this->addSqlExtras($sql, $column);
  162. return $sql;
  163. }
  164. protected function addColumn($table, $column)
  165. {
  166. $sql = "ALTER TABLE `{$table}` ADD `{$column['Field']}` " . addslashes($column['Type']);
  167. $this->addSqlExtras($sql, $column);
  168. return $sql;
  169. }
  170. protected function dropColumn($table, $column)
  171. {
  172. return "ALTER TABLE `{$table}` DROP `{$column['Field']}`";
  173. }
  174. protected function dropTable($t)
  175. {
  176. return "DROP TABLE IF EXISTS `{$t}`";
  177. }
  178. protected function checkColumnExists($column, $column_list)
  179. {
  180. foreach ($column_list as $compare_column)
  181. {
  182. if ($compare_column['Field'] === $column['Field'])
  183. {
  184. return $compare_column;
  185. }
  186. }
  187. return false;
  188. }
  189. protected function createIndexDifference($table)
  190. {
  191. $current_indexes = $this->getIndexListFromTable($table, $this->current);
  192. $published_indexes = $this->getIndexListFromTable($table, $this->published);
  193. foreach ($current_indexes as $cur_index)
  194. {
  195. $index_for_compare = $this->checkIndexExists($cur_index, $published_indexes);
  196. if (!$index_for_compare)
  197. {
  198. $this->down($this->dropConstraint($cur_index));
  199. $this->down($this->dropIndex($cur_index));
  200. $this->up($this->dropConstraint($cur_index));
  201. $this->up($this->dropIndex($cur_index));
  202. $this->up($this->addIndex($cur_index));
  203. $this->up($this->addConstraint($cur_index));
  204. }
  205. elseif($index_for_compare === $cur_index)
  206. {
  207. continue;
  208. }
  209. else // index exists but not identical
  210. {
  211. $this->down($this->dropConstraint($cur_index));
  212. $this->down($this->dropIndex($cur_index));
  213. $this->down($this->addIndex($index_for_compare));
  214. $this->down($this->addConstraint($index_for_compare));
  215. $this->up($this->dropConstraint($cur_index));
  216. $this->up($this->dropIndex($cur_index));
  217. $this->up($this->addIndex($cur_index));
  218. $this->up($this->addConstraint($cur_index));
  219. }
  220. }
  221. }
  222. protected function getIndexListFromTable($table, mysqli $connection)
  223. {
  224. $sql = "SHOW INDEXES FROM `{$table}`";
  225. $res = $connection->query($sql);
  226. $indexes = array();
  227. while ($row = $res->fetch_array(MYSQLI_ASSOC))
  228. {
  229. if (!isset($indexes[$row['Key_name']])) $indexes[$row['Key_name']] = array();
  230. $indexes[$row['Key_name']]['unique'] = !intval($row['Non_unique']);
  231. $indexes[$row['Key_name']]['type'] = $row['Index_type'];
  232. $indexes[$row['Key_name']]['name'] = $row['Key_name'];
  233. $indexes[$row['Key_name']]['table'] = $row['Table'];
  234. if (!isset($indexes[$row['Key_name']]['fields'])) $indexes[$row['Key_name']]['fields'] = array();
  235. $indexes[$row['Key_name']]['fields'][$row['Seq_in_index']] =
  236. array(
  237. 'name' => $row['Column_name'],
  238. 'length' => $row['Sub_part']
  239. );
  240. $indexes[$row['Key_name']]['constraint'] = $this->getConstraintForColumn($connection,$table,$row['Column_name']);
  241. }
  242. //var_dump($indexes);
  243. return $indexes;
  244. }
  245. protected function checkIndexExists($index, $index_list)
  246. {
  247. foreach($index_list as $comparing_index)
  248. {
  249. if($index['name']===$comparing_index['name'])
  250. {
  251. return $comparing_index;
  252. }
  253. }
  254. return false;
  255. }
  256. protected function addIndex($index)
  257. {
  258. if($index['name'] === 'PRIMARY'){
  259. $index_string = "ALTER TABLE `{$index['table']}` ADD PRIMARY KEY";
  260. $fields = array();
  261. foreach ($index['fields'] as $f)
  262. {
  263. $len = intval($f['length']) ? "({$f['length']})" : '';
  264. $fields[] = "{$f['name']}" . $len;
  265. }
  266. $index_string .= "(" . implode(',', $fields) . ")";
  267. }else{
  268. $index_string = "CREATE ";
  269. if ($index['type'] === 'FULLTEXT') $index_string .= " FULLTEXT ";
  270. if ($index['unique']) $index_string .= " UNIQUE ";
  271. $index_string .= " INDEX `{$index['name']}` ";
  272. if (in_array($index['type'], array('RTREE', 'BTREE', 'HASH', )))
  273. {
  274. $index_string .= " USING {$index['type']} ";
  275. }
  276. $index_string .= " on `{$index['table']}` ";
  277. $fields = array();
  278. foreach ($index['fields'] as $f)
  279. {
  280. $len = intval($f['length']) ? "({$f['length']})" : '';
  281. $fields[] = "{$f['name']}" . $len;
  282. }
  283. $index_string .= "(" . implode(',', $fields) . ")";
  284. }
  285. return $index_string;
  286. }
  287. protected function dropIndex($index)
  288. {
  289. return "DROP INDEX `{$index['name']}` ON `{$index['table']}`";
  290. }
  291. protected function getConstraintForColumn(mysqli $connection,$table,$col_name)
  292. {
  293. $q = "select database() as dbname";
  294. $res = $connection->query($q);
  295. $row = $res->fetch_array(MYSQLI_ASSOC);
  296. $dbname = $row['dbname'];
  297. Output::verbose("DATABASE: {$row['dbname']}");
  298. $sql = "SELECT k.CONSTRAINT_SCHEMA,k.CONSTRAINT_NAME,k.TABLE_NAME,k.COLUMN_NAME,k.REFERENCED_TABLE_NAME,k.REFERENCED_COLUMN_NAME, r.UPDATE_RULE, r.DELETE_RULE FROM information_schema.key_column_usage k LEFT JOIN information_schema.referential_constraints r ON r.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA AND k.REFERENCED_TABLE_NAME=r.REFERENCED_TABLE_NAME LEFT JOIN information_schema.table_constraints t ON t.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA WHERE k.constraint_schema='$dbname' AND t.CONSTRAINT_TYPE='FOREIGN KEY' AND k.TABLE_NAME='$table' AND r.TABLE_NAME='$table' AND t.TABLE_NAME='$table' AND k.COLUMN_NAME='$col_name'";
  299. Output::verbose($sql);
  300. $res = $connection->query($sql);
  301. $row = $res->fetch_array(MYSQLI_ASSOC);
  302. if(!count($row)) return false;
  303. $constraint = array(
  304. 'table' => $table,
  305. 'name' => $row['CONSTRAINT_NAME'],
  306. 'column' => $row['COLUMN_NAME'],
  307. 'reference' => array(
  308. 'table' => $row['REFERENCED_TABLE_NAME'],
  309. 'column' => $row['REFERENCED_COLUMN_NAME'],
  310. 'update' => $row['UPDATE_RULE'],
  311. 'delete' => $row['DELETE_RULE'],
  312. )
  313. );
  314. //echo "=================\n\n\n\=========";
  315. //var_dump($constraint);
  316. return $constraint;
  317. }
  318. protected function dropConstraint($index)
  319. {
  320. if(!isset($index['constraint']['column']) || !strlen($index['constraint']['column'])) return '';
  321. $sql = "ALTER TABLE `{$index['constraint']['table']}` ".
  322. "DROP FOREIGN KEY `{$index['constraint']['name']}` ";
  323. //echo "DELETE==================================\n$sql\n";
  324. //var_dump($index['constraint']);
  325. return $sql;
  326. }
  327. protected function addConstraint($index)
  328. {
  329. if(!isset($index['constraint']['column']) || !strlen($index['constraint']['column'])) return '';
  330. $sql = "ALTER TABLE `{$index['constraint']['table']}` ".
  331. "ADD CONSTRAINT `{$index['constraint']['name']}` ".
  332. "FOREIGN KEY (`{$index['constraint']['column']}`) ".
  333. "REFERENCES `{$index['constraint']['reference']['table']}` ".
  334. "(`{$index['constraint']['reference']['column']}`) ".
  335. "ON UPDATE {$index['constraint']['reference']['update']} ".
  336. "ON DELETE {$index['constraint']['reference']['delete']} ";
  337. //echo "ADD==================================\n$sql\n\n";
  338. //var_dump($index['constraint']);
  339. return $sql;
  340. }
  341. }