From 8a591fa16e9fba77dfbeba1f52299e32b5ac06a6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 22 Mar 2023 15:17:36 -1000 Subject: [PATCH] Add auto repairs for events schema (#90136) * Add auto repairs for events schema * Add auto repairs for events schema * Add auto repairs for events schema * Add auto repairs for events schema * Add auto repairs for events schema * fix bug - wrong table --- .../recorder/auto_repairs/events/__init__.py | 1 + .../recorder/auto_repairs/events/schema.py | 31 ++++++++ .../components/recorder/migration.py | 6 ++ .../recorder/auto_repairs/events/__init__.py | 1 + .../auto_repairs/events/test_schema.py | 76 +++++++++++++++++++ .../recorder/auto_repairs/states/__init__.py | 4 - .../auto_repairs/statistics/__init__.py | 4 - 7 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/recorder/auto_repairs/events/__init__.py create mode 100644 homeassistant/components/recorder/auto_repairs/events/schema.py create mode 100644 tests/components/recorder/auto_repairs/events/__init__.py create mode 100644 tests/components/recorder/auto_repairs/events/test_schema.py diff --git a/homeassistant/components/recorder/auto_repairs/events/__init__.py b/homeassistant/components/recorder/auto_repairs/events/__init__.py new file mode 100644 index 00000000000..66ae1a1407e --- /dev/null +++ b/homeassistant/components/recorder/auto_repairs/events/__init__.py @@ -0,0 +1 @@ +"""events repairs for Recorder.""" diff --git a/homeassistant/components/recorder/auto_repairs/events/schema.py b/homeassistant/components/recorder/auto_repairs/events/schema.py new file mode 100644 index 00000000000..e32cbd4df7f --- /dev/null +++ b/homeassistant/components/recorder/auto_repairs/events/schema.py @@ -0,0 +1,31 @@ +"""Events schema repairs.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ...db_schema import EventData, Events +from ..schema import ( + correct_db_schema_precision, + correct_db_schema_utf8, + validate_db_schema_precision, + validate_table_schema_supports_utf8, +) + +if TYPE_CHECKING: + from ... import Recorder + + +def validate_db_schema(instance: Recorder) -> set[str]: + """Do some basic checks for common schema errors caused by manual migration.""" + return validate_table_schema_supports_utf8( + instance, EventData, (EventData.shared_data,) + ) | validate_db_schema_precision(instance, Events) + + +def correct_db_schema( + instance: Recorder, + schema_errors: set[str], +) -> None: + """Correct issues detected by validate_db_schema.""" + correct_db_schema_utf8(instance, EventData, schema_errors) + correct_db_schema_precision(instance, Events, schema_errors) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 931422b64f4..0eee065a0ca 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -28,6 +28,10 @@ from homeassistant.core import HomeAssistant from homeassistant.util.enum import try_parse_enum from homeassistant.util.ulid import ulid_to_bytes +from .auto_repairs.events.schema import ( + correct_db_schema as events_correct_db_schema, + validate_db_schema as events_validate_db_schema, +) from .auto_repairs.states.schema import ( correct_db_schema as states_correct_db_schema, validate_db_schema as states_validate_db_schema, @@ -199,6 +203,7 @@ def _find_schema_errors( schema_errors: set[str] = set() schema_errors |= statistics_validate_db_schema(instance) schema_errors |= states_validate_db_schema(instance) + schema_errors |= events_validate_db_schema(instance) return schema_errors @@ -250,6 +255,7 @@ def migrate_schema( ) statistics_correct_db_schema(instance, schema_errors) states_correct_db_schema(instance, schema_errors) + events_correct_db_schema(instance, schema_errors) if current_version != SCHEMA_VERSION: instance.queue_task(PostSchemaMigrationTask(current_version, SCHEMA_VERSION)) diff --git a/tests/components/recorder/auto_repairs/events/__init__.py b/tests/components/recorder/auto_repairs/events/__init__.py new file mode 100644 index 00000000000..fca6a655ba4 --- /dev/null +++ b/tests/components/recorder/auto_repairs/events/__init__.py @@ -0,0 +1 @@ +"""Tests for Recorder component.""" diff --git a/tests/components/recorder/auto_repairs/events/test_schema.py b/tests/components/recorder/auto_repairs/events/test_schema.py new file mode 100644 index 00000000000..b19ff4ca503 --- /dev/null +++ b/tests/components/recorder/auto_repairs/events/test_schema.py @@ -0,0 +1,76 @@ +"""The test repairing events schema.""" + +# pylint: disable=invalid-name +from unittest.mock import ANY, patch + +import pytest + +from homeassistant.core import HomeAssistant + +from ...common import async_wait_recording_done + +from tests.typing import RecorderInstanceGenerator + + +@pytest.mark.parametrize("enable_schema_validation", [True]) +@pytest.mark.parametrize("db_engine", ("mysql", "postgresql")) +async def test_validate_db_schema_fix_float_issue( + async_setup_recorder_instance: RecorderInstanceGenerator, + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + db_engine, +) -> None: + """Test validating DB schema with postgresql and mysql. + + Note: The test uses SQLite, the purpose is only to exercise the code. + """ + with patch( + "homeassistant.components.recorder.core.Recorder.dialect_name", db_engine + ), patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_db_schema_precision", + return_value={"events.double precision"}, + ), patch( + "homeassistant.components.recorder.migration._modify_columns" + ) as modify_columns_mock: + await async_setup_recorder_instance(hass) + await async_wait_recording_done(hass) + + assert "Schema validation failed" not in caplog.text + assert ( + "Database is about to correct DB schema errors: events.double precision" + in caplog.text + ) + modification = [ + "time_fired_ts DOUBLE PRECISION", + ] + modify_columns_mock.assert_called_once_with(ANY, ANY, "events", modification) + + +@pytest.mark.parametrize("enable_schema_validation", [True]) +async def test_validate_db_schema_fix_utf8_issue_event_data( + async_setup_recorder_instance: RecorderInstanceGenerator, + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test validating DB schema with MySQL. + + Note: The test uses SQLite, the purpose is only to exercise the code. + """ + with patch( + "homeassistant.components.recorder.core.Recorder.dialect_name", "mysql" + ), patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_supports_utf8", + return_value={"event_data.4-byte UTF-8"}, + ): + await async_setup_recorder_instance(hass) + await async_wait_recording_done(hass) + + assert "Schema validation failed" not in caplog.text + assert ( + "Database is about to correct DB schema errors: event_data.4-byte UTF-8" + in caplog.text + ) + assert ( + "Updating character set and collation of table event_data to utf8mb4" + in caplog.text + ) diff --git a/tests/components/recorder/auto_repairs/states/__init__.py b/tests/components/recorder/auto_repairs/states/__init__.py index 6e98d881ea9..fca6a655ba4 100644 --- a/tests/components/recorder/auto_repairs/states/__init__.py +++ b/tests/components/recorder/auto_repairs/states/__init__.py @@ -1,5 +1 @@ """Tests for Recorder component.""" - -import pytest - -pytest.register_assert_rewrite("tests.components.recorder.common") diff --git a/tests/components/recorder/auto_repairs/statistics/__init__.py b/tests/components/recorder/auto_repairs/statistics/__init__.py index 6e98d881ea9..fca6a655ba4 100644 --- a/tests/components/recorder/auto_repairs/statistics/__init__.py +++ b/tests/components/recorder/auto_repairs/statistics/__init__.py @@ -1,5 +1 @@ """Tests for Recorder component.""" - -import pytest - -pytest.register_assert_rewrite("tests.components.recorder.common")