Narrow sqlite database corruption check to ensure disk image is malformed (#121947)

* Narrow sqlite database corruption check to ensure disk image is malformed

The database corruption check would also replace the database when it
locked externally instead of only when its malformed.

This was discovered in https://github.com/home-assistant/core/issues/121909#issuecomment-2227409124
when a user did a manual index creation while HA was online

* tweak

* tweak

* fix

* fix
This commit is contained in:
J. Nick Koston 2024-07-14 16:23:07 -05:00 committed by GitHub
parent 19d2d023ab
commit 73f6e3c07b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 17 additions and 5 deletions

View File

@ -1182,7 +1182,15 @@ class Recorder(threading.Thread):
def _handle_database_error(self, err: Exception) -> bool:
"""Handle a database error that may result in moving away the corrupt db."""
if isinstance(err.__cause__, sqlite3.DatabaseError):
if (
(cause := err.__cause__)
and isinstance(cause, sqlite3.DatabaseError)
and (cause_str := str(cause))
# Make sure we do not move away a database when its only locked
# externally by another process. sqlite does not give us a named
# exception for this so we have to check the error message.
and ("malformed" in cause_str or "not a database" in cause_str)
):
_LOGGER.exception(
"Unrecoverable sqlite3 database corruption detected: %s", err
)

View File

@ -1699,7 +1699,9 @@ async def test_database_corruption_while_running(
hass.states.async_set("test.lost", "on", {})
sqlite3_exception = DatabaseError("statement", {}, [])
sqlite3_exception.__cause__ = sqlite3.DatabaseError()
sqlite3_exception.__cause__ = sqlite3.DatabaseError(
"database disk image is malformed"
)
await async_wait_recording_done(hass)
with patch.object(

View File

@ -174,7 +174,9 @@ async def test_database_migration_encounters_corruption(
assert recorder.util.async_migration_in_progress(hass) is False
sqlite3_exception = DatabaseError("statement", {}, [])
sqlite3_exception.__cause__ = sqlite3.DatabaseError()
sqlite3_exception.__cause__ = sqlite3.DatabaseError(
"database disk image is malformed"
)
with (
patch(

View File

@ -204,7 +204,7 @@ async def test_purge_old_states_encouters_database_corruption(
await async_wait_recording_done(hass)
sqlite3_exception = DatabaseError("statement", {}, [])
sqlite3_exception.__cause__ = sqlite3.DatabaseError()
sqlite3_exception.__cause__ = sqlite3.DatabaseError("not a database")
with (
patch(

View File

@ -178,7 +178,7 @@ async def test_purge_old_states_encouters_database_corruption(
await async_wait_recording_done(hass)
sqlite3_exception = DatabaseError("statement", {}, [])
sqlite3_exception.__cause__ = sqlite3.DatabaseError()
sqlite3_exception.__cause__ = sqlite3.DatabaseError("not a database")
with (
patch(