├── README.md ├── arrays.php ├── db.php ├── dbDiffs.php ├── index.php ├── screen1.png ├── showDiff.php └── view.css /README.md: -------------------------------------------------------------------------------- 1 | mysqlDiff - mysql database differences 2 | ========= 3 | This is a tool that displays schema differences between two versions of same mysql databases. 4 | 5 | Example 6 | ----------- 7 | 8 | ### Database `test` 9 | 10 | Table structure for table `posts` 11 | 12 | CREATE TABLE IF NOT EXISTS `posts` ( 13 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 14 | `content` text NOT NULL, 15 | PRIMARY KEY (`id`) 16 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; 17 | 18 | Table structure for table `users` 19 | 20 | CREATE TABLE IF NOT EXISTS `users` ( 21 | `id` int(10) unsigned NOT NULL, 22 | `email` varchar(50) NOT NULL, 23 | `age` int(11) NOT NULL 24 | ) ENGINE=MyISAM DEFAULT CHARSET=latin1; 25 | 26 | 27 | ### Database: `test2` 28 | 29 | Table structure for table `logs` 30 | 31 | CREATE TABLE IF NOT EXISTS `logs` ( 32 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 33 | `log` text COLLATE utf8_unicode_ci NOT NULL, 34 | PRIMARY KEY (`id`) 35 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ; 36 | 37 | Table structure for table `users` 38 | 39 | CREATE TABLE IF NOT EXISTS `users` ( 40 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 41 | `email` varchar(50) COLLATE utf8_unicode_ci NOT NULL, 42 | `age` smallint(5) unsigned DEFAULT NULL, 43 | PRIMARY KEY (`id`) 44 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ; 45 | 46 | Sample output: 47 | 48 | ![screenshot1](https://raw.github.com/muatik/mysqlDiff/master/screen1.png "example 1") 49 | -------------------------------------------------------------------------------- /arrays.php: -------------------------------------------------------------------------------- 1 | $field.$separator; 11 | } 12 | elseif(is_array($first['value'])){ 13 | foreach($arr as $i) 14 | $cloud.=$i[$field].$separator; 15 | } 16 | $cloud=mb_substr($cloud,0,mb_strlen($separator)*-1); 17 | return $cloud; 18 | } 19 | 20 | /* @brief nesneleri diziye çevirir. 21 | * @params $field dizi değeri olaca nesne özelliği 22 | * @params $fieldKey dizi anahtarı olacak nesne özelliği 23 | * @example 24 | * $x->a=elma; $x->b=5; 25 | * print_r(arrays::convertToArray($x,'a','b')); 26 | * çıktı: Array([5] => elma) 27 | * */ 28 | public static function convertToArray($arr,$field,$keyField=null){ 29 | $newArr=''; 30 | if(count($arr)<1) return null; 31 | 32 | if($keyField==null) 33 | foreach($arr as $i) 34 | $newArr[]=$i->$field; 35 | else 36 | foreach($arr as $i) 37 | $newArr[$i->$keyField]=$i->$field; 38 | 39 | return $newArr; 40 | } 41 | 42 | public static function makeUnique($arr,$field=null){ 43 | if(count($arr)<1) return null; 44 | $narr=array(); 45 | $vals=array(); 46 | 47 | if(is_object($arr[0])){ 48 | foreach($arr as $k=>$i) 49 | if(!in_array($i,$vals)){ 50 | $narr[$k]=$i; 51 | $vals[]=$i[$field]; 52 | } 53 | } 54 | elseif(is_array($arr[0])){ 55 | foreach($arr as $k=>$i) 56 | if(!in_array($i[$field],$vals)){ 57 | $narr[$k]=$i; 58 | $vals[]=$i[$field]; 59 | } 60 | } 61 | else{ 62 | foreach($arr as $k=>$i) 63 | if(!in_array($i,$vals)){ 64 | $narr[$k]=$i; 65 | $vals[]=$i; 66 | } 67 | } 68 | 69 | return $narr; 70 | } 71 | 72 | public static function removeEmpties($arr){ 73 | foreach($arr as $k=>$v){ 74 | $v=trim($v); 75 | if($v=='') unset($arr[$k]); 76 | else $arr[$k]=$v; 77 | } 78 | return $arr; 79 | } 80 | 81 | 82 | public static function _compare($o1,$o2,$field,$action='diff'){ 83 | 84 | if(!is_array($o1)){ 85 | $o1=array($o1); 86 | $o2=array($o2); 87 | } 88 | 89 | $o1=arrays::makeCloud($o1,$field,','); 90 | $o2=arrays::makeCloud($o2,$field,','); 91 | 92 | $o1=explode(',',$o1); 93 | $o2=explode(',',$o2); 94 | 95 | if($action=='intersect') 96 | return array_intersect($o1,$o2); 97 | else 98 | return array_diff($o1,$o2); 99 | } 100 | 101 | public static function diff($o1,$o2,$field,$type='all'){ 102 | $i1=array(); 103 | $i2=array(); 104 | 105 | switch($type){ 106 | case 'to': 107 | $i1=arrays::_compare($o1,$o2,$field,'diff'); 108 | break; 109 | case 'from': 110 | $i1=arrays::_compare($o2,$o1,$field,'diff'); 111 | break; 112 | default: 113 | $i1=arrays::_compare($o1,$o2,$field,'diff'); 114 | $i2=arrays::_compare($o2,$o1,$field,'diff'); 115 | } 116 | 117 | return array_merge($i1,$i2); 118 | } 119 | 120 | public static function intersect($o1,$o2,$field){ 121 | return arrays::_compare($o2,$o1,$field,'intersect'); 122 | } 123 | 124 | } 125 | ?> 126 | -------------------------------------------------------------------------------- /db.php: -------------------------------------------------------------------------------- 1 | host=constant('_dbHost'); 32 | $this->username=constant('_dbUser'); 33 | $this->password=constant('_dbPassword'); 34 | if(constant('_dbDatabase')!=null) 35 | $this->database=constant('_dbDatabase'); 36 | } 37 | 38 | } 39 | 40 | function connect(){ 41 | if($this->connection=new mysqli($this->host,$this->username,$this->password)){ 42 | $this->query('set names "'.$this->charSet.'" collate "'.$this->collate.'"'); 43 | if(@$this->connection->select_db($this->database)){ 44 | $this->query('set names "'.$this->charSet.'" collate "'.$this->collate.'"'); 45 | return true; 46 | } 47 | $this->error='Veri tabaný seçilemedi.'; 48 | return false; 49 | } 50 | $this->error='Veri tabaný sunucusuna baðlanýlamadý.';return false; 51 | } 52 | 53 | function query($sql,$buffered=true){ 54 | $this->affectedRows=0; 55 | $this->numRows=0; 56 | if(!$this->connection && !$this->connect()) return false; 57 | if(($buffered && $this->reader=$this->connection->query($sql)) || 58 | (!$buffered && $this->reader=$this->connection->query($sql))){ 59 | 60 | if(gettype($this->reader)=='object') 61 | $this->numRows=$this->reader->num_rows; 62 | else 63 | $this->affectedRows=$this->connection->affected_rows; 64 | 65 | return true; 66 | } 67 | $this->error='Sorgu çalýþtýrýlamadý.';return false; 68 | } 69 | 70 | function unbufferedQuery($sql){ 71 | return $this->query($sql,false); 72 | } 73 | 74 | function fetchObject(){ 75 | return $this->reader->fetch_object(); 76 | } 77 | function fetchArray(){ 78 | return $this->reader->fetch_array(); 79 | } 80 | function fetchRow(){ 81 | return $this->reader->fetch_row(); 82 | } 83 | function nextIncrement($t){ 84 | $this->query('show table status like \''.$t.'\''); 85 | $nau=$this->fetchObject(); // next auto_increment 86 | return $nau->Auto_increment; 87 | } 88 | function lastIncrement($t){ 89 | return $this->nextIncrement($t)-1; 90 | } 91 | function getInsertId(){ 92 | return $this->connection->insert_id; 93 | } 94 | function getError(){ 95 | return $this->connection->error; 96 | } 97 | function fetchListByQuery($sql,$style='object'){ 98 | if($this->query($sql) ){ 99 | $arr=array(); 100 | if($style=='object') 101 | while($r=$this->fetchObject()) $arr[]=$r; 102 | elseif($style=='array') 103 | while($r=$this->fetchArray()) $arr[]=$r; 104 | elseif($style=='row') 105 | while($r=$this->fetchRow()) $arr[]=$r; 106 | return $arr; 107 | } 108 | return false; 109 | } 110 | function fetchFirstRecord($q){ 111 | if($this->query($q)){ 112 | if($this->numRows>0) 113 | return $this->fetchObject(); 114 | } 115 | return false; 116 | } 117 | 118 | /* 119 | * aşağıdakiler yeni metodlardır. 120 | * üstekilerle değiştirilecektir zamanla 121 | * */ 122 | public function fetch($sql,$style='object'){ 123 | return $this->fetchListByQuery($sql,$style='object'); 124 | } 125 | public function fetchFirst($q){ 126 | return $this->fetchFirstRecord($q); 127 | } 128 | 129 | public function escape($s,$strip=true){ 130 | 131 | if(!$this->connection && !$this->connect()) return false; 132 | if(!is_array($s)){ 133 | if($strip){ 134 | if(strpos($s,'\\\'')!==false || strpos($s,'\\"')!==false) 135 | $s=stripslashes($s); 136 | } 137 | return $this->connection->real_escape_string($s); 138 | } 139 | else{ 140 | 141 | if($strip){ 142 | foreach($s as $k=>$i) 143 | if(strpos($i,'\\\'')!==false || strpos($i,'\\"')!==false) 144 | $s[$k]=stripslashes($i); 145 | } 146 | foreach($s as $k=>$i) 147 | $s[$k]=$this->connection->real_escape_string($i); 148 | 149 | return $s; 150 | } 151 | } 152 | } 153 | ?> 154 | -------------------------------------------------------------------------------- /dbDiffs.php: -------------------------------------------------------------------------------- 1 | tableFields=array( 20 | 'ENGINE','TABLE_COLLATION','TABLE_COMMENT' 21 | ); 22 | 23 | // sütun karşılaştırma kriterleri 24 | $this->columnFields=array( 25 | 'COLUMN_TYPE','COLUMN_DEFAULT','IS_NULLABLE', 26 | 'CHARACTER_SET_NAME','COLLATION_NAME', 27 | 'COLUMN_KEY','EXTRA','COLUMN_COMMENT' 28 | ); 29 | 30 | $this->db=new db(); 31 | $this->db->database='information_schema'; 32 | } 33 | 34 | /** 35 | * Adı belirtilen iki veritabanını arasındaki farklılıkları bulur 36 | * @param db1 birinci veritabanının adı 37 | * @param db2 ikinci veritabanının adı 38 | * @return array 39 | * */ 40 | public function check($db1Name,$db2Name){ 41 | $diffs=new stdClass; 42 | 43 | $db1=new stdClass; 44 | $db2=new stdClass; 45 | $db1->tables=$this->fetchTables($db1Name); 46 | $db2->tables=$this->fetchTables($db2Name); 47 | 48 | // db1'in db2'den farklı tabloları alınıyor 49 | $diffs->tableDiff=arrays::diff( 50 | $db2->tables,$db1->tables,'TABLE_NAME','to' 51 | ); 52 | // db2'in db1'den farklı tabloları alınıyor 53 | $diffs->tableDiffr=arrays::diff( 54 | $db2->tables,$db1->tables,'TABLE_NAME','from' 55 | ); 56 | 57 | // ikisinde de olan tablo listesi hazırlanıyor 58 | $tables=arrays::intersect( 59 | $db1->tables,$db2->tables,'TABLE_NAME' 60 | ); 61 | 62 | 63 | 64 | foreach($tables as $t){ 65 | 66 | // iki tablo yapısı karşılaştırılıyor 67 | $tblDiff=$this->compareObjects( 68 | $db1->tables[$t], 69 | $db2->tables[$t], 70 | $this->tableFields 71 | ); 72 | 73 | 74 | if(count($tblDiff)>0) 75 | $diffs->tables[$t]->structure=$tblDiff; 76 | 77 | 78 | 79 | // tablo sütunları çekiliyor 80 | $t1Clm=$this->fetchColumns($db1Name,$t); 81 | $t2Clm=$this->fetchColumns($db2Name,$t); 82 | 83 | // db1 tablosu ile db2 tablosu arasındaki sütun 84 | // farklılıklarına bakılıyor 85 | $diffs->tables[$t]->columnDiff=arrays::diff( 86 | $t1Clm,$t2Clm,'COLUMN_NAME','to' 87 | ); 88 | // ve tam tersi, db2'nin db1'den farkları 89 | $diffs->tables[$t]->columnDiffr=arrays::diff( 90 | $t1Clm,$t2Clm,'COLUMN_NAME','from' 91 | ); 92 | 93 | // ikisinde de olan sütun listesi hazırlanıyor 94 | $columns=arrays::intersect( 95 | $t1Clm,$t2Clm,'COLUMN_NAME' 96 | ); 97 | 98 | 99 | foreach($columns as $c){ 100 | 101 | // iki tablo yapısı karşılaştırılıyor 102 | $cDiff=$this->compareObjects( 103 | $t1Clm[$c], 104 | $t2Clm[$c], 105 | $this->columnFields 106 | ); 107 | 108 | if(count($cDiff)>0) 109 | $diffs->tables[$t]->columns[$c]=$cDiff; 110 | } 111 | 112 | } 113 | 114 | return $diffs; 115 | 116 | } 117 | 118 | 119 | /** 120 | * iki nesneyi belirtilen alanlara göre karşılaştırır ve 121 | * uyuşmazlıkları verir. 122 | * @param object o1 ilk nesne 123 | * @param object o2 ikinci nesne 124 | * @param filelds karşılaştırmanın yapılacağı nesne özellikleri 125 | * @return array farklılıklar 126 | * */ 127 | private function compareObjects($o1,$o2,$fields){ 128 | 129 | $diffs=array(); 130 | foreach($fields as $f){ 131 | 132 | if($o1->$f==$o2->$f) continue; 133 | 134 | $diff=array('field'=>$f,'value1'=>$o1->$f, 135 | 'value2'=>$o2->$f 136 | ); 137 | 138 | $diffs[]=$diff; 139 | } 140 | return $diffs; 141 | } 142 | 143 | 144 | /** 145 | * Belirtilen veritabanındaki tablo kayıtlarını verir. 146 | * @param db veritabanının adı 147 | * */ 148 | public function fetchTables($db){ 149 | $sql='select * from TABLES 150 | where TABLE_SCHEMA=\''.$db.'\''; 151 | 152 | $rs=$this->db->fetch($sql); 153 | 154 | $rs2=array(); 155 | foreach($rs as $i) 156 | $rs2[$i->TABLE_NAME]=$i; 157 | return $rs2; 158 | } 159 | 160 | /** 161 | * Belirtilen tablodaki sütun kayıtlarını verir. 162 | * */ 163 | public function fetchColumns($db,$table){ 164 | $sql='select * from COLUMNS 165 | where TABLE_SCHEMA=\''.$db.'\' and TABLE_NAME=\''.$table.'\''; 166 | $rs=$this->db->fetch($sql); 167 | 168 | $rs2=array(); 169 | foreach($rs as $i) 170 | $rs2[$i->COLUMN_NAME]=$i; 171 | return $rs2; 172 | } 173 | 174 | /** 175 | * Erişim iznine sahip veritabanlarının listesini verir. 176 | * */ 177 | public function getDbList(){ 178 | return $this->db->fetch( 179 | 'select SCHEMA_NAME as name from SCHEMATA' 180 | ); 181 | } 182 | } 183 | 184 | 185 | ?> 186 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Db Diffs 5 | 6 | 7 | 8 | 9 | 10 | db->username='root'; 20 | $dd->db->password='root'; 21 | 22 | $dbs=$dd->getDbList(); 23 | 24 | 25 | $select=''; 29 | 30 | echo '
'; 31 | echo 'db1: '.sprintf($select,'db1').' db2: '.sprintf($select,'db2'); 32 | echo ' 33 |
'; 34 | 35 | if(isset($r['db1'],$r['db2'])){ 36 | $diff=$dd->check($r['db2'],$r['db1']); 37 | echo '
38 |

DIFFERENCES:

39 |
40 | Tables absent in '.$r['db2'].' 41 | Tables absent in '.$r['db1'].' 42 |
'; 43 | showDiffs($diff); 44 | } 45 | ?> 46 | 47 | mustafaatik.com 48 | 49 | 50 | -------------------------------------------------------------------------------- /screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muatik/mysqlDiff/bf93be65cebd74eb1df6ef6d7782e3903ea5db3c/screen1.png -------------------------------------------------------------------------------- /showDiff.php: -------------------------------------------------------------------------------- 1 | '; 4 | 5 | foreach($diff->tableDiff as $i) 6 | echo '
  • '.$i.'
  • '; 7 | foreach($diff->tableDiffr as $i) 8 | echo '
  • '.$i.'
  • '; 9 | 10 | foreach($diff->tables as $t=>$i){ 11 | 12 | if(!( 13 | isset($i->structure) 14 | || count($i->columnDiff)>0 15 | || count($i->columnDiffr)>0 16 | || isset($i->columns) 17 | )) 18 | continue; 19 | 20 | echo '
  • '.$t.'
  • '; 44 | continue; 45 | } 46 | 47 | echo '
  • COLUMNS:
  • '; 68 | 69 | 70 | echo ''; 71 | } 72 | echo ''; 73 | } 74 | ?> 75 | -------------------------------------------------------------------------------- /view.css: -------------------------------------------------------------------------------- 1 | body{font:14px helvetica;} 2 | b.table{font:bold 18px helvetica;} 3 | b.group{font:bold 15px helvetica;color:#124990} 4 | b.column{font:bold 15px helvetica;color:#222} 5 | b.exists{color:#00aa00} 6 | b.absent{color:#df4000} 7 | b.field{font:bold italic 12px helvetica;color:#786868} 8 | li.value1{font:12px helvetica;color:#019645} 9 | li.value2{font:12px helvetica;color:#911605} 10 | 11 | .legend span{padding:2px 4px;color:#fff;font:bold 12px arial;} 12 | .legend .db1{background-color:#019645} 13 | .legend .db2{background-color:#df4000} 14 | 15 | hr.footer{margin-top:60px} 16 | --------------------------------------------------------------------------------