Script zum Vergleichen zweier SQL-Dumps

Aktuell stellte sich das Problem, dass aus Versehen Dateien gelöscht wurden. Das Log gab keine Details her, aber es existierten tägliche SQL-Dumps der Datenbank. Ich habe dazu ein Script geschrieben, welches diese Dumps nimmt, auf die relevanten Zeilen kürzt und diese vergleicht. Da das vielleicht auch mal jemand anderes gebrauchen kann (oder mein Zukunfts-Ich) poste ich das Script mal hier.

Voraussetzungen:

  • Die SQL-Dateien liegen im gleichen Ordner und heißen nach diesem Muster: „projectname_new_some-more-details.mysql“ und „projectname_old_some-more-details.mysql“
  • Die Datein sind bereits entpackt und liegen als *.mysql-Dateien vor. Das ging schneller als das Entzippen noch reinzuprogrammieren. Für andere Dateienendungen ist das Script im oberen Teil anzupassen.
  • Im konkreten Fall ging es mir nur um die Tabelle „file_managed“. Aber das lässt sich ebenfalls leicht anpassen.

Der Aufruf sieht dann so aus: „php diff-sql-files.php ./path/to/the/diff/folder“

Und hier das Script:

<?php

// Get the directory.
$targetDirectory = realpath($argv[1]);
if (empty($targetDirectory) || !is_dir($targetDirectory)) {
  echo "Target directory not found\n";
  exit(1);
}

// List the .mysql files.
$mysqlFiles = glob($targetDirectory . "/*.mysql");
if (empty($mysqlFiles)) {
  echo "No mysql files found in '{$targetDirectory}'.\n";
  exit(1);
}

// Build file pairs.
$pairs = [];
foreach ($mysqlFiles as $filePath) {
  // Get the project name.
  $fileName = basename($filePath);
  $nameParts = explode('_', $fileName);
  $projectName = $nameParts[0];
  $oldOrNew = $nameParts[1];
  $pairs[$projectName][$oldOrNew] = $filePath;
}

// Handle each file pair.
try {
  foreach ($pairs as $projectName => $filePair) {
    // Trim both files.
    $oldTrimmed = trimFile($filePair['old']);
    $newTrimmed = trimFile($filePair['new']);

    // Expand Insert Statements for both files.
    $oldTrimmed = expandFile($oldTrimmed);
    $newTrimmed = expandFile($newTrimmed);

    // Build a diff file.
    $diffFile = buildDiff($oldTrimmed, $newTrimmed);

    // Cleanup files.
    unlink($filePair['old']);
    unlink($filePair['new']);
    unlink($oldTrimmed);
    unlink($newTrimmed);

    // Output something.
    echo " - Done: {$projectName}\n";
  }
}
catch (Exception $e) {
  echo "An error occurred: {$e->getMessage()}\n";
  exit(1);
}

echo "done\n";


function trimFile(string $filePath): string {
  // Open new file.
  $filePathTrimmed = $filePath . '.trimmed';
  if (!$fpTrimmed = fopen($filePathTrimmed, "a")) {
    throw new Exception("Error in writing trimmed file for '{$filePath}'.");
  }

  // Go through file to trim line by line.
  $fp = fopen($filePath, "r");
  if ($fp) {
    while (($line = fgets($fp)) !== false) {
      if (str_starts_with($line, 'INSERT INTO `file_managed`')) {
        if (fwrite($fpTrimmed, $line) === FALSE) {
          throw new Exception("Error in writing to file '{$filePath}.trimmed'.");
        }
      }
    }
    fclose($fp);
    fclose($fpTrimmed);
  }

  return $filePathTrimmed;
}

function expandFile(string $filePath): string {
  // Get the file content.
  $fileContent = file($filePath);

  // Empty the file and prepare writing to it again.
  if (!$fp = fopen($filePath, "w")) {
    throw new Exception("Error in writing expanded trimmed file '{$filePath}'.");
  }

  // Expand the insert statements to one per line.
  foreach ($fileContent as $fileLine) {
    $matches = [];
    $pattern = '/(\(\'.+?\'\))/';
    if (preg_match_all($pattern, $fileLine, $matches) === FALSE || empty($matches)) {
      throw new Exception("An error occurred during matching of INSERT statements.");
    }

    // preg_match_all puts the whole matches in the first array index, so use this
    $matches = $matches[0];
    if (fwrite($fp, implode("\n", $matches) . "\n") === FALSE) {
      throw new Exception("Error in writing to file '{$filePath}'.");
    }
  }

  // Close the file.
  fclose($fp);

  return $filePath;
}

function buildDiff(string $filePath1, string $filePath2): string {
  $content1 = file($filePath1);
  $content2 = file($filePath2);

  $filesMissingInNew = array_diff($content1, $content2);

  $newFilePath = str_replace(['_old_', '.trimmed'], ['_', '.diff'], $filePath1);
  if (file_put_contents($newFilePath, $filesMissingInNew) === false) {
    throw new Exception("Error on writing the diff file '{$newFilePath}'.");
  }

  return $newFilePath;
}

Das erzeugt zu den beiden Eingabedateien eine Ausgabedatei „projectname_some-more-details.mysql.diff“, welche nur die Zeilen enthält, die in der „alten“ Datei enthalten sind und in der „neuen“ nicht. Es geht hier also um gelöschte Daten, neu hinzugekommene werden nicht ausgegeben. Aber auch das ließe sich leicht nachrüsten. Eine Dateigröße von 0 heißt, dass es keinen Unterschied gibt.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Bitte beachte die Kommentarregeln: 1) Kein Spam, und bitte höflich bleiben. 2) Ins Namensfeld gehört ein Name. Gerne ein Pseudonym, aber bitte keine Keywords. 3) Keine kommerziellen Links, außer es hat Bezug zum Beitrag. mehr Details...

So, noch mal kurz drüber schauen und dann nichts wie ab damit. Vielen Dank fürs Kommentieren! :-)