porównywanie baz MySQL w PHP

Często przy pracy nad aplikacjami z bazą SQL zdarza się sytuacja, kiedy baza ‘deweloperska’ uległa nieudokumentowanym dokładnie zmianom, i wreszcie trzeba je wdrożyć w wersji produkcyjnej. Oczywiście lepiej byłoby prowadzić dziennik zmian :) Kiedy jednak już jest za późno, przydaje się poniższy skrypt do porównywania baz. Wychwytuje on różnice na poziomie definicji tabel i pól, proponując stosowne polecenia typu CREATE, ALTER, itp. Propozycje te nie są doskonałe, trzeba zwracać uwagę zwłaszcza na klucze i wartości domyślne, ale na pewno ułatwia synchronizację.

Skrypt napisany na podstawie MySQL Database Diff Script, ale znacznie rozszerzony i zmodyfikowany. Enjoy! :)

<html>
<head>
<style>
.deleted{
	text-decoration:line-through;
	color:red;
}
.added{
	color:green;
}
</style>
</head>
<body>
< ?php
/**
 * original by Adam Young http://adamyoung.net/
 * modified and extended 2009-09-26 by ptrk http://nobigwords.ntxt.net/ 
 * 
 */
$src_host = '';
$src_user = 'root';
$src_pass = '';
$src_db = 'baza32_superskl_emisja';
 
$dst_host = '';
$dst_user = 'root';
$dst_pass = '';
$dst_db = 'baza32_superskl';
 
$src = mysql_connect($src_host, $src_user, $src_pass);
if (!mysql_select_db($src_db, $src)) die("Could not find/USE source database: {$src_db}\n");
 
$src_tables = getTables($src);
 
 
$dst = mysql_connect($dst_host, $dst_user, $dst_pass);
if (!mysql_select_db($dst_db, $dst)) die("Could not find/USE destination database: {$dst_db}\n");
 
$dst_tables = getTables($dst);
 
 
foreach ($src_tables as $t => $table) {
	$i = 0;
	$found = false;
 
	if(isset($dst_tables[$t])){
		$dst_table = $dst_tables[$t];
		if ($dst_table->name == $table->name) {
			$diff = compareDefinitions($table->definition, $dst_table->definition);
			if (count($diff) == 0){
				echo "{$table->name}<br />";
			}else{
				echo "<b>{$table->name} is different</b><br /><ul>";
				$alters = '';
				foreach($diff as $col => $info){
					echo "<li>$col : " . $info['info'] . "</li>";
					$alters .= "alter table `" . $dst_table->name . "` " . $info['alter'] . ";\n";
				}
				echo '</ul>';
				echo "&gt;pre&lt;" . $alters . " &gt;/pre&lt;";
			}
			unset($dst_tables[$i]);
			$found = true;
		}
	}else{
		echo "<span class='deleted'>{$table->name} </span><br />";
		echo "&gt;pre&lt;{$table->create}&gt;/pre&lt;<br />";
	}
}
 
function getTables($link) {
	$rsrc = mysql_query('SHOW TABLES', $link);
	$tables = array();
	while ($row = mysql_fetch_row($rsrc)) {
		$table = new table(
			$row[0],
			getTableDef($link, $row[0]),
			getTableCreate($link, $row[0])
		);
		$tables[$row[0]] = $table;
	}
	return $tables;
}
 
function getTableDef($link, $table) {
	$rsrc = mysql_query("DESCRIBE `{$table}`");
	$result = array();
	while($row = mysql_fetch_row($rsrc)){
		list($name, $type, $null, $key, $default, $extra) = $row;
		$result[$name] = array($type, $null, $key, $default, $extra);
	}
	return $result;
}
 
function getTableCreate($link, $table) {
	$rsrc = mysql_query("SHOW CREATE TABLE `{$table}`");
	$row = mysql_fetch_row($rsrc);
	$result = $row[1];
	return $result;
}
 
class table {
	var $name;
	var $definition;
	var $create;
	function table($name, $def, $create) {
		$this->name = $name;
		$this->definition = $def;
		$this->create = $create;
	}
}
 
function compareDefinitions($defA, $defB){
	$result = array();
	foreach($defA as $col => $colDefA){
		if(!isset($defB[$col])){
			$result[$col]['info'] = "<span class='deleted'>deleted </span>";
			$result[$col]['alter'] = "drop column `" . $col . "`";
		}else{
			if(implode(',',$colDefA) != implode(',',$defB[$col])){
 
				list($typeA, $nullA, $keyA, $defaultA, $extraA) = $colDefA;
				list($typeB, $nullB, $keyB, $defaultB, $extraB) = $defB[$col];
				$info = '';
				if($typeA != $typeB) $info .= "different type $typeA/$typeB, ";
				if($nullA != $nullB) $info .= "null $nullA/$nullB, ";
				if($keyA != $keyB) $info .= "key: $keyA/$keyB, ";
				if($defaultA != $defaultB) $info .= "default: $defaultA/$defaultB, ";
				if($extraA != $extraB) $info .= "extra: $extraA/$extraB, ";
				$result[$col]['info'] = trim($info,', ');
				$result[$col]['alter'] =	"change `$col` `$col` $typeA ";
				$result[$col]['alter'] .=	($defaultA == '') ? '' : "default '$defaultA' ";
				$result[$col]['alter'] .=	($nullA == 'NO') ? 'NOT NULL ' : '';
				$result[$col]['alter'] .=	"$extraA ";
 
			}
			unset($defB[$col]);
		}
	}
	foreach($defB as $colB => $colDefB){
		list($typeB, $nullB, $keyB, $defaultB, $extraB) = $colDefB;
		$result[$colB]['info'] = "<span class='added'>added</span>";
		$result[$colB]['alter'] = "add `$colB` $typeB ";
 
	}
	return $result;
}
 
 
function errors($link){
	return mysql_errno($link) . ": " . mysql_error($link) . "\n";
}
?>
</body>
</html>

puścić coś w tle

Chcę móc uruchomić proces w tle za pomocą przeglądarki. Docelowo wszystko będzie działać na linkuksie, ale do testów przydałoby się to mieć u siebie. W linkuksie wiem.

costam &

uruchamia proces w tle, potem fajnie mogę sobie zarządzać

jobs

. Ale ja chcę w Windows. Windows Vista, żeby nie było niedomówień. Wywołanie komendy przez exec działa, ale czeka na zakończenie, a nie o to chodzi. Proces ma działać długo, a ja tylko chcę monitorować jego stan, a nie zamulać Firefoksa.

exec("start /b c:/sciezka_do_php/php.exe C:/sciezka_do_skryptu_procesu/bgproces.php > test.txt");

Próbowałem też uruchomić jakiś batch (costam.bat), który z kolei miałby uruchomić skrypt PHP, ale to też nie zadziałało - cały czas przeglądarka czekała na zakończenie całego procesu. Rozwiązanie jest takie, że w PHP na Windows trzeba użyć klasy COM, czyli windowsowych mechanizmów OLE (Object Linking and Embedding). Wygląda to tak:

< ?php
/*
 * skrypt do uruchomienia w tle skryptTla.php
 */
$fname = 'c:/log.html'; 
for($i=0; $i&lt;10; $i++){
	sleep(1);
	file_put_contents($fname, date('H:i:s') . '<br/>', FILE_APPEND);
}
file_put_contents($fname, '<hr />', FILE_APPEND);
?>
< ?php
/**
 * skrypt widziany przez przeglądarkę
 */
echo "uruchamiam " . date('H:i:s ');
 
  if(isset($_SERVER['PWD']))
  {
  	// $cmd = '....';
    $nullResult = "php $cmd > /dev/null &";
  }else{
    $cmd = 'sciezka_do_php/php.exe sciezkaSkryptuDoUruchomieniaWTle/skryptTla.php';
    $ws = new COM("WScript.Shell");
    $oExec = $ws->Run($cmd, 0, false);
  }
 
echo " koniec " . date('H:i:s ');
echo " OK ";
 
 
?>

jak sprytnie zamienić & na & amp;?

Zdarza się, że w tekstach jakie przygotowujemy do wyświetlania na stronach WWW część tzw. entycji jest już zakodowana poprawnie, a część nie. Mnie zdarzyło się tak ze znaczkiem &, który normalnie powinien w XMLu i XHTMLu być kodowany jako &amp; Niektóre ampersandy były zakodowane, ale większość pozostała w formie “wizualnej”, czyli jednego znaku. Parser XMLa tego nie lubi. Ja nie lubię dłubania. Lubię za to wyrażenia regularne, więc takim oto krótkim poleceniem zamieniam wszystkie nieprawidłowo zakodowane ‘&’ na prawidłowe ‘&amp;’, pozostawiając bez zmian te dobrze zapisane:

$tekst = preg_replace('/&(?!amp;)/', '&amp;', $tekst);

W wyrażeniu regularnym użyłem przewidywania czyli poprosiłem o takie pasujące fragmenty, po których nie następuje ciąg “amp;”. Proste.

Przy okazji okazało się, że wordpress ma problem z zapisaniem &amp; w tytule posta… :)

Next Page »