Files
kaffeekasse-saas/scripts/migrate-table-prefix.php
T

151 lines
4.7 KiB
PHP

<?php
declare(strict_types=1);
require __DIR__ . '/dev-db.php';
if (PHP_SAPI !== 'cli') {
http_response_code(404);
exit;
}
$options = getopt('', ['source-prefix:', 'target-prefix:', 'replace']);
$sourcePrefix = app_validate_db_table_prefix((string)($options['source-prefix'] ?? ''));
$targetOption = $options['target-prefix'] ?? null;
if (!is_string($targetOption) || trim($targetOption) === '') {
fwrite(STDERR, "Aufruf: php scripts/migrate-table-prefix.php --target-prefix=test_ --replace [--source-prefix=alt_]\n");
exit(1);
}
$targetPrefix = app_validate_db_table_prefix($targetOption);
if ($sourcePrefix === $targetPrefix) {
fwrite(STDERR, "Quell- und Ziel-Prefix müssen verschieden sein.\n");
exit(1);
}
if (!array_key_exists('replace', $options)) {
fwrite(STDERR, "Abbruch: --replace ist erforderlich, weil vorhandene Ziel-Testdaten ersetzt werden.\n");
exit(1);
}
/** @return string */
$quoteIdentifier = static function (string $name): string {
return '`' . str_replace('`', '``', $name) . '`';
};
$rawPdo = dev_pdo_for_prefix('');
$targetPdo = dev_pdo_for_prefix($targetPrefix);
echo "Zielschema {$targetPrefix} wird auf den aktuellen Migrationsstand gebracht.\n";
$applied = dev_apply_migrations($targetPdo);
echo count($applied) . " neue Migration(en) für {$targetPrefix} angewendet.\n";
$tableExists = static function (PDO $pdo, string $table): bool {
$stmt = $pdo->prepare(
'SELECT 1 FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?'
);
$stmt->execute([$table]);
return $stmt->fetchColumn() !== false;
};
/** @return list<string> */
$tableColumns = static function (PDO $pdo, string $table): array {
$stmt = $pdo->prepare(
'SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
ORDER BY ORDINAL_POSITION'
);
$stmt->execute([$table]);
return array_map('strval', $stmt->fetchAll(PDO::FETCH_COLUMN));
};
$targetsToClear = [];
$copies = [];
$sourceTableCount = 0;
foreach (app_database_table_names() as $baseName) {
if ($baseName === 'schema_migrations') {
continue;
}
$sourceTable = app_db_table_name($baseName, $sourcePrefix);
$targetTable = app_db_table_name($baseName, $targetPrefix);
$sourceExists = $tableExists($rawPdo, $sourceTable);
$targetExists = $tableExists($rawPdo, $targetTable);
if ($sourceExists) {
$sourceTableCount++;
}
if ($sourceExists && !$targetExists) {
throw new RuntimeException("Zieltabelle {$targetTable} fehlt nach den Migrationen.");
}
if (!$targetExists) {
continue;
}
$targetsToClear[] = $targetTable;
if (!$sourceExists) {
continue;
}
$sourceColumns = $tableColumns($rawPdo, $sourceTable);
$targetColumns = $tableColumns($rawPdo, $targetTable);
if ($sourceColumns !== $targetColumns) {
throw new RuntimeException("Spalten von {$sourceTable} und {$targetTable} stimmen nicht überein.");
}
$copies[] = [$sourceTable, $targetTable, $sourceColumns];
}
if ($sourceTableCount === 0) {
throw new RuntimeException('Im Quellschema wurden keine Anwendungstabellen gefunden.');
}
$copiedRows = 0;
$rawPdo->exec('SET FOREIGN_KEY_CHECKS = 0');
try {
$rawPdo->beginTransaction();
foreach ($targetsToClear as $targetTable) {
$rawPdo->exec('DELETE FROM ' . $quoteIdentifier($targetTable));
}
foreach ($copies as [$sourceTable, $targetTable, $columns]) {
$quotedColumns = implode(', ', array_map($quoteIdentifier, $columns));
$sourceRows = (int)$rawPdo
->query('SELECT COUNT(*) FROM ' . $quoteIdentifier($sourceTable))
->fetchColumn();
if ($sourceRows > 0) {
$rawPdo->exec(
'INSERT INTO ' . $quoteIdentifier($targetTable)
. ' (' . $quotedColumns . ') SELECT ' . $quotedColumns
. ' FROM ' . $quoteIdentifier($sourceTable)
);
}
$targetRows = (int)$rawPdo
->query('SELECT COUNT(*) FROM ' . $quoteIdentifier($targetTable))
->fetchColumn();
if ($sourceRows !== $targetRows) {
throw new RuntimeException(
"Zeilenzahl stimmt für {$sourceTable} -> {$targetTable} nicht: {$sourceRows} != {$targetRows}."
);
}
$copiedRows += $targetRows;
echo "{$sourceTable} -> {$targetTable}: {$targetRows} Zeile(n)\n";
}
$rawPdo->commit();
} catch (Throwable $e) {
if ($rawPdo->inTransaction()) {
$rawPdo->rollBack();
}
throw $e;
} finally {
$rawPdo->exec('SET FOREIGN_KEY_CHECKS = 1');
}
echo "Datenübernahme abgeschlossen: {$copiedRows} Zeile(n). Das Quellschema blieb unverändert.\n";