mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 19:57:07 +00:00
Revert "Refactor recorder migration"
This reverts commit 69e10e59821f7e5ca1d4d305079f059774b67864.
This commit is contained in:
parent
3a5b66fd60
commit
4a1c40f09b
@ -588,31 +588,24 @@ class Recorder(threading.Thread):
|
|||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Start processing events to save."""
|
"""Start processing events to save."""
|
||||||
setup_result = self._setup_recorder()
|
current_version = self._setup_recorder()
|
||||||
|
|
||||||
if not setup_result:
|
if current_version is None:
|
||||||
# Give up if we could not connect
|
|
||||||
self.hass.add_job(self.async_connection_failed)
|
self.hass.add_job(self.async_connection_failed)
|
||||||
return
|
return
|
||||||
|
|
||||||
schema_status = migration.validate_db_schema(self.hass, self.get_session)
|
self.schema_version = current_version
|
||||||
if schema_status is None:
|
|
||||||
# Give up if we could not validate the schema
|
|
||||||
self.hass.add_job(self.async_connection_failed)
|
|
||||||
return
|
|
||||||
self.schema_version = schema_status.current_version
|
|
||||||
|
|
||||||
schema_is_valid = migration.schema_is_valid(schema_status)
|
schema_is_current = migration.schema_is_current(current_version)
|
||||||
|
if schema_is_current:
|
||||||
if schema_is_valid:
|
|
||||||
self._setup_run()
|
self._setup_run()
|
||||||
else:
|
else:
|
||||||
self.migration_in_progress = True
|
self.migration_in_progress = True
|
||||||
self.migration_is_live = migration.live_migration(schema_status)
|
self.migration_is_live = migration.live_migration(current_version)
|
||||||
|
|
||||||
self.hass.add_job(self.async_connection_success)
|
self.hass.add_job(self.async_connection_success)
|
||||||
|
|
||||||
if self.migration_is_live or schema_is_valid:
|
if self.migration_is_live or schema_is_current:
|
||||||
# If the migrate is live or the schema is current, we need to
|
# If the migrate is live or the schema is current, we need to
|
||||||
# wait for startup to complete. If its not live, we need to continue
|
# wait for startup to complete. If its not live, we need to continue
|
||||||
# on.
|
# on.
|
||||||
@ -630,8 +623,8 @@ class Recorder(threading.Thread):
|
|||||||
self.hass.add_job(self.async_set_db_ready)
|
self.hass.add_job(self.async_set_db_ready)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not schema_is_valid:
|
if not schema_is_current:
|
||||||
if self._migrate_schema_and_setup_run(schema_status):
|
if self._migrate_schema_and_setup_run(current_version):
|
||||||
self.schema_version = SCHEMA_VERSION
|
self.schema_version = SCHEMA_VERSION
|
||||||
if not self._event_listener:
|
if not self._event_listener:
|
||||||
# If the schema migration takes so long that the end
|
# If the schema migration takes so long that the end
|
||||||
@ -696,14 +689,14 @@ class Recorder(threading.Thread):
|
|||||||
# happens to rollback and recover
|
# happens to rollback and recover
|
||||||
self._reopen_event_session()
|
self._reopen_event_session()
|
||||||
|
|
||||||
def _setup_recorder(self) -> bool:
|
def _setup_recorder(self) -> None | int:
|
||||||
"""Create a connection to the database."""
|
"""Create connect to the database and get the schema version."""
|
||||||
tries = 1
|
tries = 1
|
||||||
|
|
||||||
while tries <= self.db_max_retries:
|
while tries <= self.db_max_retries:
|
||||||
try:
|
try:
|
||||||
self._setup_connection()
|
self._setup_connection()
|
||||||
return True
|
return migration.get_schema_version(self.get_session)
|
||||||
except UnsupportedDialect:
|
except UnsupportedDialect:
|
||||||
break
|
break
|
||||||
except Exception as err: # pylint: disable=broad-except
|
except Exception as err: # pylint: disable=broad-except
|
||||||
@ -715,16 +708,14 @@ class Recorder(threading.Thread):
|
|||||||
tries += 1
|
tries += 1
|
||||||
time.sleep(self.db_retry_wait)
|
time.sleep(self.db_retry_wait)
|
||||||
|
|
||||||
return False
|
return None
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_migration_started(self) -> None:
|
def _async_migration_started(self) -> None:
|
||||||
"""Set the migration started event."""
|
"""Set the migration started event."""
|
||||||
self.async_migration_event.set()
|
self.async_migration_event.set()
|
||||||
|
|
||||||
def _migrate_schema_and_setup_run(
|
def _migrate_schema_and_setup_run(self, current_version: int) -> bool:
|
||||||
self, schema_status: migration.SchemaValidationStatus
|
|
||||||
) -> bool:
|
|
||||||
"""Migrate schema to the latest version."""
|
"""Migrate schema to the latest version."""
|
||||||
persistent_notification.create(
|
persistent_notification.create(
|
||||||
self.hass,
|
self.hass,
|
||||||
@ -736,7 +727,7 @@ class Recorder(threading.Thread):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
migration.migrate_schema(
|
migration.migrate_schema(
|
||||||
self, self.hass, self.engine, self.get_session, schema_status
|
self, self.hass, self.engine, self.get_session, current_version
|
||||||
)
|
)
|
||||||
except exc.DatabaseError as err:
|
except exc.DatabaseError as err:
|
||||||
if self._handle_database_error(err):
|
if self._handle_database_error(err):
|
||||||
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
import contextlib
|
import contextlib
|
||||||
from dataclasses import dataclass
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, cast
|
from typing import TYPE_CHECKING, cast
|
||||||
@ -62,9 +61,8 @@ def raise_if_exception_missing_str(ex: Exception, match_substrs: Iterable[str])
|
|||||||
raise ex
|
raise ex
|
||||||
|
|
||||||
|
|
||||||
def get_schema_version(session_maker: Callable[[], Session]) -> int | None:
|
def get_schema_version(session_maker: Callable[[], Session]) -> int:
|
||||||
"""Get the schema version."""
|
"""Get the schema version."""
|
||||||
try:
|
|
||||||
with session_scope(session=session_maker()) as session:
|
with session_scope(session=session_maker()) as session:
|
||||||
res = (
|
res = (
|
||||||
session.query(SchemaChanges)
|
session.query(SchemaChanges)
|
||||||
@ -80,47 +78,16 @@ def get_schema_version(session_maker: Callable[[], Session]) -> int | None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return cast(int, current_version)
|
return cast(int, current_version)
|
||||||
except Exception as err: # pylint: disable=broad-except
|
|
||||||
_LOGGER.exception("Error when determining DB schema version: %s", err)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
def schema_is_current(current_version: int) -> bool:
|
||||||
class SchemaValidationStatus:
|
|
||||||
"""Store schema validation status."""
|
|
||||||
|
|
||||||
current_version: int
|
|
||||||
|
|
||||||
|
|
||||||
def _schema_is_current(current_version: int) -> bool:
|
|
||||||
"""Check if the schema is current."""
|
"""Check if the schema is current."""
|
||||||
return current_version == SCHEMA_VERSION
|
return current_version == SCHEMA_VERSION
|
||||||
|
|
||||||
|
|
||||||
def schema_is_valid(schema_status: SchemaValidationStatus) -> bool:
|
def live_migration(current_version: int) -> bool:
|
||||||
"""Check if the schema is valid."""
|
|
||||||
return _schema_is_current(schema_status.current_version)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_db_schema(
|
|
||||||
hass: HomeAssistant, session_maker: Callable[[], Session]
|
|
||||||
) -> SchemaValidationStatus | None:
|
|
||||||
"""Check if the schema is valid.
|
|
||||||
|
|
||||||
This checks that the schema is the current version as well as for some common schema
|
|
||||||
errors caused by manual migration between database engines, for example importing an
|
|
||||||
SQLite database to MariaDB.
|
|
||||||
"""
|
|
||||||
current_version = get_schema_version(session_maker)
|
|
||||||
if current_version is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return SchemaValidationStatus(current_version)
|
|
||||||
|
|
||||||
|
|
||||||
def live_migration(schema_status: SchemaValidationStatus) -> bool:
|
|
||||||
"""Check if live migration is possible."""
|
"""Check if live migration is possible."""
|
||||||
return schema_status.current_version >= LIVE_MIGRATION_MIN_SCHEMA_VERSION
|
return current_version >= LIVE_MIGRATION_MIN_SCHEMA_VERSION
|
||||||
|
|
||||||
|
|
||||||
def migrate_schema(
|
def migrate_schema(
|
||||||
@ -128,14 +95,13 @@ def migrate_schema(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
engine: Engine,
|
engine: Engine,
|
||||||
session_maker: Callable[[], Session],
|
session_maker: Callable[[], Session],
|
||||||
schema_status: SchemaValidationStatus,
|
current_version: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Check if the schema needs to be upgraded."""
|
"""Check if the schema needs to be upgraded."""
|
||||||
current_version = schema_status.current_version
|
|
||||||
_LOGGER.warning("Database is about to upgrade. Schema version: %s", current_version)
|
_LOGGER.warning("Database is about to upgrade. Schema version: %s", current_version)
|
||||||
db_ready = False
|
db_ready = False
|
||||||
for version in range(current_version, SCHEMA_VERSION):
|
for version in range(current_version, SCHEMA_VERSION):
|
||||||
if live_migration(SchemaValidationStatus(version)) and not db_ready:
|
if live_migration(version) and not db_ready:
|
||||||
db_ready = True
|
db_ready = True
|
||||||
instance.migration_is_live = True
|
instance.migration_is_live = True
|
||||||
hass.add_job(instance.async_set_db_ready)
|
hass.add_job(instance.async_set_db_ready)
|
||||||
|
@ -134,16 +134,14 @@ async def test_database_migration_encounters_corruption(hass):
|
|||||||
sqlite3_exception.__cause__ = sqlite3.DatabaseError()
|
sqlite3_exception.__cause__ = sqlite3.DatabaseError()
|
||||||
|
|
||||||
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch(
|
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch(
|
||||||
"homeassistant.components.recorder.migration._schema_is_current",
|
"homeassistant.components.recorder.migration.schema_is_current",
|
||||||
side_effect=[False],
|
side_effect=[False, True],
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.recorder.migration.migrate_schema",
|
"homeassistant.components.recorder.migration.migrate_schema",
|
||||||
side_effect=sqlite3_exception,
|
side_effect=sqlite3_exception,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.recorder.core.move_away_broken_database"
|
"homeassistant.components.recorder.core.move_away_broken_database"
|
||||||
) as move_away, patch(
|
) as move_away:
|
||||||
"homeassistant.components.recorder.Recorder._schedule_compile_missing_statistics",
|
|
||||||
):
|
|
||||||
recorder_helper.async_initialize_recorder(hass)
|
recorder_helper.async_initialize_recorder(hass)
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
hass, "recorder", {"recorder": {"db_url": "sqlite://"}}
|
hass, "recorder", {"recorder": {"db_url": "sqlite://"}}
|
||||||
@ -161,8 +159,8 @@ async def test_database_migration_encounters_corruption_not_sqlite(hass):
|
|||||||
assert recorder.util.async_migration_in_progress(hass) is False
|
assert recorder.util.async_migration_in_progress(hass) is False
|
||||||
|
|
||||||
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch(
|
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch(
|
||||||
"homeassistant.components.recorder.migration._schema_is_current",
|
"homeassistant.components.recorder.migration.schema_is_current",
|
||||||
side_effect=[False],
|
side_effect=[False, True],
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.recorder.migration.migrate_schema",
|
"homeassistant.components.recorder.migration.migrate_schema",
|
||||||
side_effect=DatabaseError("statement", {}, []),
|
side_effect=DatabaseError("statement", {}, []),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user