From 13ce0a7d6ac7aed9dc50a1bfd7e12339621bc3c5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 May 2022 10:56:50 -0500 Subject: [PATCH] Fix history using pre v25 queries during v26 migration (#71295) --- homeassistant/components/recorder/core.py | 5 +++++ homeassistant/components/recorder/history.py | 4 ++-- tests/components/recorder/test_history.py | 6 +++--- tests/components/recorder/test_init.py | 7 +++++++ tests/components/recorder/test_migrate.py | 7 ++++++- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index 7f07a4483cb..84509a1bd53 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -44,6 +44,7 @@ from .const import ( ) from .executor import DBInterruptibleThreadPoolExecutor from .models import ( + SCHEMA_VERSION, Base, EventData, Events, @@ -161,6 +162,7 @@ class Recorder(threading.Thread): self.entity_filter = entity_filter self.exclude_t = exclude_t + self.schema_version = 0 self._commits_without_expire = 0 self._old_states: dict[str, States] = {} self._state_attributes_ids: LRU = LRU(STATE_ATTRIBUTES_ID_CACHE_SIZE) @@ -502,6 +504,8 @@ class Recorder(threading.Thread): self.hass.add_job(self.async_connection_failed) return + self.schema_version = current_version + schema_is_current = migration.schema_is_current(current_version) if schema_is_current: self._setup_run() @@ -523,6 +527,7 @@ class Recorder(threading.Thread): # with startup which is also cpu intensive if not schema_is_current: if self._migrate_schema_and_setup_run(current_version): + self.schema_version = SCHEMA_VERSION if not self._event_listener: # If the schema migration takes so long that the end # queue watcher safety kicks in because MAX_QUEUE_BACKLOG diff --git a/homeassistant/components/recorder/history.py b/homeassistant/components/recorder/history.py index 24fe21f101c..d221ced3a84 100644 --- a/homeassistant/components/recorder/history.py +++ b/homeassistant/components/recorder/history.py @@ -116,7 +116,7 @@ def query_and_join_attributes( # If we in the process of migrating schema we do # not want to join the state_attributes table as we # do not know if it will be there yet - if recorder.get_instance(hass).migration_in_progress: + if recorder.get_instance(hass).schema_version < 25: return QUERY_STATES_PRE_SCHEMA_25, False # Finally if no migration is in progress and no_attributes # was not requested, we query both attributes columns and @@ -146,7 +146,7 @@ def bake_query_and_join_attributes( # If we in the process of migrating schema we do # not want to join the state_attributes table as we # do not know if it will be there yet - if recorder.get_instance(hass).migration_in_progress: + if recorder.get_instance(hass).schema_version < 25: if include_last_updated: return ( bakery(lambda session: session.query(*QUERY_STATES_PRE_SCHEMA_25)), diff --git a/tests/components/recorder/test_history.py b/tests/components/recorder/test_history.py index 5e29fb092b1..20b60c3c96d 100644 --- a/tests/components/recorder/test_history.py +++ b/tests/components/recorder/test_history.py @@ -665,7 +665,7 @@ async def test_state_changes_during_period_query_during_migration_to_schema_25( conn.execute(text("drop table state_attributes;")) conn.commit() - with patch.object(instance, "migration_in_progress", True): + with patch.object(instance, "schema_version", 24): no_attributes = True hist = history.state_changes_during_period( hass, start, end, entity_id, no_attributes, include_start_time_state=False @@ -711,7 +711,7 @@ async def test_get_states_query_during_migration_to_schema_25( conn.execute(text("drop table state_attributes;")) conn.commit() - with patch.object(instance, "migration_in_progress", True): + with patch.object(instance, "schema_version", 24): no_attributes = True hist = await _async_get_states( hass, end, [entity_id], no_attributes=no_attributes @@ -760,7 +760,7 @@ async def test_get_states_query_during_migration_to_schema_25_multiple_entities( conn.execute(text("drop table state_attributes;")) conn.commit() - with patch.object(instance, "migration_in_progress", True): + with patch.object(instance, "schema_version", 24): no_attributes = True hist = await _async_get_states( hass, end, entity_ids, no_attributes=no_attributes diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 9bfca76394b..18b74df0189 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -26,6 +26,7 @@ from homeassistant.components.recorder import ( ) from homeassistant.components.recorder.const import DATA_INSTANCE, KEEPALIVE_TIME from homeassistant.components.recorder.models import ( + SCHEMA_VERSION, EventData, Events, RecorderRuns, @@ -459,6 +460,12 @@ def _state_with_context(hass, entity_id): return hass.states.get(entity_id) +def test_setup_without_migration(hass_recorder): + """Verify the schema version without a migration.""" + hass = hass_recorder() + assert recorder.get_instance(hass).schema_version == SCHEMA_VERSION + + # pylint: disable=redefined-outer-name,invalid-name def test_saving_state_include_domains(hass_recorder): """Test saving and restoring a state.""" diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index 1b84eb5d171..0a95d174d66 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -22,7 +22,11 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components import persistent_notification as pn, recorder from homeassistant.components.recorder import migration, models from homeassistant.components.recorder.const import DATA_INSTANCE -from homeassistant.components.recorder.models import RecorderRuns, States +from homeassistant.components.recorder.models import ( + SCHEMA_VERSION, + RecorderRuns, + States, +) from homeassistant.components.recorder.util import session_scope import homeassistant.util.dt as dt_util @@ -80,6 +84,7 @@ async def test_migration_in_progress(hass): await async_wait_recording_done(hass) assert recorder.util.async_migration_in_progress(hass) is False + assert recorder.get_instance(hass).schema_version == SCHEMA_VERSION async def test_database_migration_failed(hass):