fix: non-ok integrity verdicts are corruption, escaped file URIs, proven no-WAL-transition test
- a non-ok integrity_check verdict raises DatabaseError carrying the NOTADB marker so it classifies as corruption unconditionally; ANY DatabaseError escaping the probe propagates (no more swallowing structural problems as non-corruption) - file URI built via Path.resolve().as_uri() so #/?/% in paths cannot mis-target the probe - migration-stage test now spies on NestQuestDatabase.open and asserts the read-write open was NEVER reached: the whole-file preflight rejecting corruption before the WAL transition is the mechanism under test, and the byte-identical + no-sidecar assertions prove it - store guard permits SELECT + PRAGMA integrity_check on the ro-conn
This commit is contained in:
@@ -74,7 +74,9 @@ async def _preflight_existing_file(
|
||||
"""
|
||||
if not db_path.exists() or db_path.stat().st_size == 0:
|
||||
return
|
||||
uri = f"file:{db_path}?mode=ro"
|
||||
# Properly escape the path: '#'/'?'/'%' in a directory or file name
|
||||
# would otherwise be parsed as URI fragments/parameters/escapes.
|
||||
uri = db_path.resolve().as_uri() + "?mode=ro"
|
||||
|
||||
def _probe() -> None:
|
||||
conn = sqlite3.connect(uri, uri=True)
|
||||
@@ -84,8 +86,12 @@ async def _preflight_existing_file(
|
||||
conn.close()
|
||||
verdict = row[0] if row else "missing"
|
||||
if verdict != "ok":
|
||||
# A non-ok integrity verdict IS corruption: raise with the
|
||||
# verdict text (which matches the corruption markers) and
|
||||
# the NOTADB code so classification is code-based.
|
||||
raise sqlite3.DatabaseError(
|
||||
f"integrity_check on read-only probe failed: {verdict}"
|
||||
f"integrity_check verdict {verdict!r} on read-only "
|
||||
f"probe: file is not a database"
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -93,15 +99,18 @@ async def _preflight_existing_file(
|
||||
# harness's async_add_executor_job is awaitable too).
|
||||
await hass.async_add_executor_job(_probe)
|
||||
except sqlite3.DatabaseError as err:
|
||||
# Non-ok integrity verdicts are corruption, full stop: there is
|
||||
# no benign reading of a failed whole-file integrity check, so
|
||||
# ANY DatabaseError out of the probe that carries a corruption
|
||||
# marker or code propagates as corruption. Only exotic probe
|
||||
# failures that classify as non-corruption (e.g. a transient
|
||||
# 'database is locked' on the read) are downgraded to a debug
|
||||
# log, letting the real open path surface them.
|
||||
if _is_corruption_error(err):
|
||||
raise
|
||||
# Read-only probing can fail for non-corruption reasons (e.g.
|
||||
# a transient lock); the real open path will surface those.
|
||||
_LOGGER.debug(
|
||||
"NestQuest preflight read of %s failed (non-corruption): %s",
|
||||
db_path,
|
||||
err,
|
||||
)
|
||||
raise sqlite3.DatabaseError(
|
||||
f"read-only corruption probe of {db_path} failed: {err}"
|
||||
) from err
|
||||
|
||||
async def _async_open_database(
|
||||
hass: HomeAssistant, db_path: Path
|
||||
|
||||
Reference in New Issue
Block a user