Dev build 2026-06-01 21:32
This commit is contained in:
parent
53a1e30475
commit
9e9ba825e0
@ -778,17 +778,13 @@ def _real_restore_errors(stderr: str) -> list[str]:
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
def _run_pg_restore(dump_bytes: bytes) -> None:
|
def _apply_pg_dump(dump_bytes: bytes) -> None:
|
||||||
"""Restore a full PostgreSQL dump.
|
"""Reset the public schema and load a dump into it.
|
||||||
|
|
||||||
Resets the public schema first so any plain pg_dump (with or without
|
|
||||||
--clean) restores cleanly into an empty schema. This is destructive: it
|
|
||||||
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
|
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
|
target server doesn't recognize (e.g. a newer pg_dump emitting
|
||||||
`transaction_timeout` against an older server) don't abort the restore.
|
`transaction_timeout` against an older server) don't abort the load.
|
||||||
stderr is then inspected: any non-benign `ERROR:` line fails the restore.
|
stderr is then inspected: any non-benign `ERROR:` line raises.
|
||||||
"""
|
"""
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["PGPASSWORD"] = os.environ.get("POSTGRES_PASSWORD", "")
|
env["PGPASSWORD"] = os.environ.get("POSTGRES_PASSWORD", "")
|
||||||
@ -821,6 +817,35 @@ def _run_pg_restore(dump_bytes: bytes) -> None:
|
|||||||
tmp_path.unlink(missing_ok=True)
|
tmp_path.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_pg_restore(dump_bytes: bytes) -> None:
|
||||||
|
"""Restore a full PostgreSQL dump, with automatic rollback on failure.
|
||||||
|
|
||||||
|
Before the destructive load, a safety dump of the CURRENT database is taken.
|
||||||
|
If applying the requested dump fails, the database is rolled back to that
|
||||||
|
safety snapshot so a failed restore never leaves an empty/broken database.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
safety_bytes, _ = _run_pg_dump()
|
||||||
|
except Exception:
|
||||||
|
safety_bytes = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
_apply_pg_dump(dump_bytes)
|
||||||
|
except Exception as restore_err:
|
||||||
|
if safety_bytes is not None:
|
||||||
|
try:
|
||||||
|
_apply_pg_dump(safety_bytes)
|
||||||
|
except Exception as rollback_err:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"restore failed: {restore_err}; AND rollback failed: {rollback_err}. "
|
||||||
|
"Database may be in an inconsistent state."
|
||||||
|
)
|
||||||
|
raise RuntimeError(
|
||||||
|
f"restore failed and was rolled back to the pre-restore state: {restore_err}"
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def _list_pg_dump_paths(client: dropbox.Dropbox, postgres_root: str) -> list[str]:
|
def _list_pg_dump_paths(client: dropbox.Dropbox, postgres_root: str) -> list[str]:
|
||||||
files = _dropbox_list_files_recursive(client, postgres_root)
|
files = _dropbox_list_files_recursive(client, postgres_root)
|
||||||
return sorted([p for p in files if p.endswith(".sql")], reverse=True)
|
return sorted([p for p in files if p.endswith(".sql")], reverse=True)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from changelog import CHANGELOG
|
from changelog import CHANGELOG
|
||||||
|
|
||||||
BUILD = 3
|
BUILD = 4
|
||||||
|
|
||||||
|
|
||||||
def _release_version() -> str:
|
def _release_version() -> str:
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
# Develop Changelog
|
# Develop Changelog
|
||||||
|
|
||||||
|
## 2026-06-01 — Full Database Restore: safety dump + automatic rollback
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Full Database Restore could leave the database **empty** if the dump failed to load: the restore dropped and recreated the public schema first and only then applied the dump, so any failure during the load (e.g. a header `transaction_timeout` error with `ON_ERROR_STOP`) wiped all data with nothing to fall back to.
|
||||||
|
- `routers/backup.py`: split the load into `_apply_pg_dump`; `_run_pg_restore` now takes a safety `pg_dump` of the current database before the destructive load and, if applying the requested dump fails, automatically rolls back to that safety snapshot. A failed restore therefore no longer leaves an empty or broken database.
|
||||||
|
|
||||||
## 2026-06-01 — Full Database Restore: tolerate PostgreSQL version mismatch
|
## 2026-06-01 — Full Database Restore: tolerate PostgreSQL version mismatch
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user