Full restore onto PostgreSQL: counters of restored tables go after their data when the backup records none (MySQL, SQLite); test MariaDB onto PostgreSQL

This commit is contained in:
2026-10-20 23:04:53 +02:00
parent e461c48056
commit ac57e31c93
4 changed files with 46 additions and 5 deletions
+24 -3
View File
@@ -216,7 +216,7 @@ final class DbDumper {
}
$report['restored'][] = $table;
}
$this->restoreSequences($manifest['sequences'] ?? [], $report['missing']);
$this->restoreSequences($manifest['sequences'] ?? [], $report['missing'], $report['restored']);
$this->resumeForeignKeys($foreignKeys);
$this->db->commit();
// Foreign keys of the tables created above, now their rows are in. Not inside the
@@ -573,16 +573,21 @@ final class DbDumper {
return $name;
}
/** @param list<string> $leftOut tables not restored (--skip-missing-tables): they do not exist here */
private function restoreSequences(array $sequences, array $leftOut = []): void {
/**
* @param list<string> $leftOut tables not restored (--skip-missing-tables): they do not exist here
* @param list<string> $restored tables restored: their other counters go after their data
*/
private function restoreSequences(array $sequences, array $leftOut = [], array $restored = []): void {
if (!$this->db->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform) {
return;
}
$leftOut = array_flip($leftOut);
$set = [];
foreach ($sequences as $info) {
if (isset($leftOut[$info['table']])) {
continue;
}
$set[$info['table'] . '.' . $info['column']] = true;
$seq = $this->db->fetchOne('SELECT pg_get_serial_sequence(quote_ident(?), ?)', [$info['table'], $info['column']]);
if ($seq === false || $seq === null) {
continue;
@@ -597,5 +602,21 @@ final class DbDumper {
[\Doctrine\DBAL\ParameterType::STRING, \Doctrine\DBAL\ParameterType::INTEGER, \Doctrine\DBAL\ParameterType::BOOLEAN]);
}
}
// A backup from MySQL/MariaDB or SQLite records no counters (those derive them from the
// data): every other counter of a restored table goes after its data, or new rows (a new
// file, an activity) would collide with restored ones.
$restored = array_flip($restored);
$rows = $this->db->fetchAllAssociative(
"SELECT c.table_name, c.column_name, pg_get_serial_sequence(quote_ident(c.table_name), c.column_name) AS seq
FROM information_schema.columns c
WHERE c.table_schema = current_schema() AND pg_get_serial_sequence(quote_ident(c.table_name), c.column_name) IS NOT NULL");
foreach ($rows as $r) {
if (!isset($restored[$r['table_name']]) || isset($set[$r['table_name'] . '.' . $r['column_name']])) {
continue;
}
$max = (int)$this->db->fetchOne(sprintf('SELECT COALESCE(MAX(%s), 0) FROM %s',
$this->db->quoteIdentifier($r['column_name']), $this->db->quoteIdentifier($r['table_name'])));
$this->db->executeQuery('SELECT setval(CAST(? AS regclass), ?, false)', [$r['seq'], $max + 1]);
}
}
}