fix: a restore with pg_restore 17 into Postgres 16 isn't stopped by a setting the server lacks
CI and images / lint (push) Successful in 1s
CI and images / extension-version (push) Successful in 2s
CI and images / backend-lint-and-test (push) Successful in 48s
CI and images / frontend-build (push) Successful in 24s
CI and images / extension-test (push) Successful in 20s
CI and images / integration (push) Successful in 2m51s
CI and images / sign-extension (push) Successful in 2s
CI and images / build-agent (push) Successful in 4s
CI and images / build-web (push) Successful in 1m52s
CI and images / smoke-web (push) Successful in 55s
CI and images / promote (push) Successful in 2s
CI and images / refresh-dev (push) Skipped
CI and images / unattended-failure (push) Skipped
CI and images / lint (push) Successful in 1s
CI and images / extension-version (push) Successful in 2s
CI and images / backend-lint-and-test (push) Successful in 48s
CI and images / frontend-build (push) Successful in 24s
CI and images / extension-test (push) Successful in 20s
CI and images / integration (push) Successful in 2m51s
CI and images / sign-extension (push) Successful in 2s
CI and images / build-agent (push) Successful in 4s
CI and images / build-web (push) Successful in 1m52s
CI and images / smoke-web (push) Successful in 55s
CI and images / promote (push) Successful in 2s
CI and images / refresh-dev (push) Skipped
CI and images / unattended-failure (push) Skipped
pg_restore 17 opens with `SET transaction_timeout = 0`, which Postgres 16 refuses. Under --exit-on-error that one harmless statement ended every restore: the image ships pg_restore 17.11 and the bundled server is pg16, so a restore from the bucket (and the local restore) could never have loaded a dump. The backup round trip, now that CI has pg_dump, found it. pg_restore now runs to the end and its errors are judged afterwards: the known harmless one is ignored, and any other fails the restore with that error's words rather than the "Command was:" line that followed it. Also sorts an import the lint lane flagged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -347,17 +347,50 @@ def wipe_db(*, db_url: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
# A pg_restore newer than the server sets parameters the server doesn't have
|
||||
# (pg_restore 17 against Postgres 16: `SET transaction_timeout = 0`). The
|
||||
# statement fails and nothing depends on it, so its error is not a failed
|
||||
# restore.
|
||||
_HARMLESS_RESTORE_ERRORS = (
|
||||
'unrecognized configuration parameter "transaction_timeout"',
|
||||
)
|
||||
|
||||
|
||||
def _restore_errors(stderr: str) -> list[str]:
|
||||
"""pg_restore's error lines, less the harmless ones."""
|
||||
return [
|
||||
line.strip() for line in stderr.splitlines()
|
||||
if "error:" in line and not any(h in line for h in _HARMLESS_RESTORE_ERRORS)
|
||||
]
|
||||
|
||||
|
||||
def restore_db(*, db_url: str, sql_path: Path) -> None:
|
||||
"""Wipe the public schema, then load the custom-format dump. pg_restore
|
||||
stops at the first error, which RestoreFailed carries; the database is
|
||||
then partly loaded, and the caller decides what to put back."""
|
||||
"""Wipe the public schema, then load the custom-format dump. Any error
|
||||
pg_restore reports, other than a harmless one, raises RestoreFailed with
|
||||
the first; the database is then partly loaded, and the caller decides what
|
||||
to put back."""
|
||||
wipe_db(db_url=db_url)
|
||||
# Custom-format (-Fc) dumps are restored with pg_restore, not psql.
|
||||
_run_reporting(
|
||||
_gently(["pg_restore", "--no-owner", "--no-acl", "--exit-on-error",
|
||||
"-d", _libpq_url(db_url), str(sql_path)]),
|
||||
_RESTORE_SUBPROCESS_TIMEOUT_S,
|
||||
)
|
||||
# Custom-format (-Fc) dumps are restored with pg_restore, not psql. It runs
|
||||
# on past errors (it can't skip the harmless one and stop at the rest), so
|
||||
# its errors are judged once it is done.
|
||||
cmd = _gently(["pg_restore", "--no-owner", "--no-acl",
|
||||
"-d", _libpq_url(db_url), str(sql_path)])
|
||||
with tempfile.TemporaryFile() as err:
|
||||
proc = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=err,
|
||||
timeout=_RESTORE_SUBPROCESS_TIMEOUT_S)
|
||||
if proc.returncode == 0:
|
||||
return
|
||||
err.seek(0)
|
||||
said = err.read().decode(errors="replace")
|
||||
errors = _restore_errors(said)
|
||||
if errors:
|
||||
raise RestoreFailed(errors[0] if len(errors) == 1
|
||||
else f"{errors[0]} ({len(errors)} errors in all)")
|
||||
if not any(h in said for h in _HARMLESS_RESTORE_ERRORS):
|
||||
# A failure with no error line to show for it.
|
||||
lines = said.strip().splitlines()
|
||||
raise RestoreFailed(lines[-1].strip() if lines
|
||||
else f"pg_restore exited with {proc.returncode}")
|
||||
|
||||
|
||||
def restore_images(*, images_root: Path, tar_path: Path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user