Dev build 2026-06-01 21:24

This commit is contained in:
Ivo Oskamp 2026-06-01 21:24:02 +02:00
parent 6df47a8161
commit 40c8525691
3 changed files with 38 additions and 4 deletions

View File

@ -756,12 +756,39 @@ def _psql_base_args() -> list[str]:
] ]
# Session-GUC SET statements a dump may carry that an OLDER server doesn't know.
# These are emitted in the pg_dump header and are harmless to skip — they only
# affect the restoring session, not the data. (e.g. `transaction_timeout` was
# introduced in PostgreSQL 17; restoring such a dump into a <17 server errors on
# that line.) We must not let these abort an otherwise valid restore, but any
# OTHER error still fails the restore.
_BENIGN_RESTORE_ERROR_MARKERS = (
"unrecognized configuration parameter",
)
def _real_restore_errors(stderr: str) -> list[str]:
errors = []
for line in (stderr or "").splitlines():
if "ERROR:" not in line:
continue
if any(marker in line for marker in _BENIGN_RESTORE_ERROR_MARKERS):
continue
errors.append(line.strip())
return errors
def _run_pg_restore(dump_bytes: bytes) -> None: def _run_pg_restore(dump_bytes: bytes) -> None:
"""Restore a full PostgreSQL dump. """Restore a full PostgreSQL dump.
Resets the public schema first so any plain pg_dump (with or without Resets the public schema first so any plain pg_dump (with or without
--clean) restores cleanly into an empty schema. This is destructive: it --clean) restores cleanly into an empty schema. This is destructive: it
drops and recreates the entire public schema before applying the dump. drops and recreates the entire public schema before applying the dump.
The dump is applied WITHOUT ON_ERROR_STOP so that benign header SETs the
target server doesn't recognize (e.g. a newer pg_dump emitting
`transaction_timeout` against an older server) don't abort the restore.
stderr is then inspected: any non-benign `ERROR:` line fails the restore.
""" """
env = os.environ.copy() env = os.environ.copy()
env["PGPASSWORD"] = os.environ.get("POSTGRES_PASSWORD", "") env["PGPASSWORD"] = os.environ.get("POSTGRES_PASSWORD", "")
@ -782,13 +809,14 @@ def _run_pg_restore(dump_bytes: bytes) -> None:
try: try:
proc = subprocess.run( proc = subprocess.run(
["psql", *base, "-v", "ON_ERROR_STOP=1", "-f", str(tmp_path)], ["psql", *base, "-f", str(tmp_path)],
env=env, env=env,
capture_output=True, capture_output=True,
text=True, text=True,
) )
if proc.returncode != 0: real_errors = _real_restore_errors(proc.stderr)
raise RuntimeError(f"psql restore failed: {(proc.stderr or '').strip()[:500] or 'unknown error'}") if real_errors:
raise RuntimeError("psql restore failed: " + " | ".join(real_errors)[:500])
finally: finally:
tmp_path.unlink(missing_ok=True) tmp_path.unlink(missing_ok=True)

View File

@ -10,7 +10,7 @@ from __future__ import annotations
from changelog import CHANGELOG from changelog import CHANGELOG
BUILD = 1 BUILD = 2
def _release_version() -> str: def _release_version() -> str:

View File

@ -1,5 +1,11 @@
# Develop Changelog # Develop Changelog
## 2026-06-01 — Full Database Restore: tolerate PostgreSQL version mismatch
### Fixed
- Full Database Restore failed with `ERROR: unrecognized configuration parameter "transaction_timeout"` when the dump was produced by a newer `pg_dump` (PostgreSQL 17+, which emits `SET transaction_timeout = 0;` in the header) but restored into an older server (<17) that doesn't know that session parameter. The restore ran with `ON_ERROR_STOP=1` and aborted on that harmless header line.
- `routers/backup.py` (`_run_pg_restore`): the dump is now applied without `ON_ERROR_STOP`; stderr is inspected afterwards via `_real_restore_errors`, which ignores benign "unrecognized configuration parameter" errors but still fails the restore on any other `ERROR:` line. The schema-reset step keeps `ON_ERROR_STOP=1` (it is our own controlled SQL).
## 2026-06-01 — Backup/Restore: database-stored books are now restorable ## 2026-06-01 — Backup/Restore: database-stored books are now restorable
### Fixed ### Fixed