From 56fbef8357b2d24804e214e8fb118e76e850b8fe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 10 Mar 2023 11:30:14 -1000 Subject: [PATCH] coverage --- .../components/recorder/db_schema.py | 2 +- homeassistant/components/recorder/purge.py | 6 +- homeassistant/components/recorder/queries.py | 6 +- tests/components/recorder/test_purge.py | 112 ++++++++++++++++++ 4 files changed, 119 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/recorder/db_schema.py b/homeassistant/components/recorder/db_schema.py index 6e7230e4023..3ac1b55bef2 100644 --- a/homeassistant/components/recorder/db_schema.py +++ b/homeassistant/components/recorder/db_schema.py @@ -239,7 +239,7 @@ class Events(Base): """Return string representation of instance for debugging.""" return ( "" ) diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index a02b34fe529..da865c750ad 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -571,10 +571,10 @@ def _purge_old_recorder_runs( def _purge_old_event_types(instance: Recorder, session: Session) -> None: """Purge all old event types.""" # Event types is small, no need to batch run it - purged_event_types = set() + purge_event_types = set() event_type_ids = set() for event_type_id, event_type in session.execute(find_event_types_to_purge()): - purged_event_types.add(event_type) + purge_event_types.add(event_type) event_type_ids.add(event_type_id) if not event_type_ids: @@ -584,7 +584,7 @@ def _purge_old_event_types(instance: Recorder, session: Session) -> None: _LOGGER.debug("Deleted %s event types", deleted_rows) # Evict any entries in the event_type cache referring to a purged state - instance.event_type_manager.evict_purged(purged_event_types) + instance.event_type_manager.evict_purged(purge_event_types) def _purge_filtered_data(instance: Recorder, session: Session) -> bool: diff --git a/homeassistant/components/recorder/queries.py b/homeassistant/components/recorder/queries.py index ddc04d820a1..6420d285dca 100644 --- a/homeassistant/components/recorder/queries.py +++ b/homeassistant/components/recorder/queries.py @@ -743,9 +743,9 @@ def find_event_types_to_purge() -> StatementLambdaElement: lambda: select(EventTypes.event_type_id, EventTypes.event_type).where( EventTypes.event_type_id == ( - select( - distinct(Events.event_type_id).label("unused_event_type_id") - ).filter(Events.event_type_id.not_in(select(EventTypes.event_type_id))) + select(distinct(EventTypes.event_type_id).label("unused_event_type_id")) + .filter(EventTypes.event_type_id.not_in(select(Events.event_type_id))) + .subquery() ).c.unused_event_type_id ) ) diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 07c935129e9..fcabb2e83a8 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -16,6 +16,7 @@ from homeassistant.components.recorder.const import ( from homeassistant.components.recorder.db_schema import ( EventData, Events, + EventTypes, RecorderRuns, StateAttributes, States, @@ -31,6 +32,7 @@ from homeassistant.components.recorder.tasks import PurgeTask from homeassistant.components.recorder.util import session_scope from homeassistant.const import EVENT_STATE_CHANGED, EVENT_THEMES_UPDATED, STATE_ON from homeassistant.core import HomeAssistant +from homeassistant.helpers.json import json_dumps from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt as dt_util @@ -1684,3 +1686,113 @@ async def test_purge_can_mix_legacy_and_new_format( # does not prevent future purges. Its ignored. assert states_with_event_id.count() == 0 assert states_without_event_id.count() == 1 + + +async def test_purge_old_events_purges_the_event_type_ids( + async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant +) -> None: + """Test deleting old events purges event type ids.""" + instance = await async_setup_recorder_instance(hass) + assert instance.event_type_manager.active is True + + utcnow = dt_util.utcnow() + five_days_ago = utcnow - timedelta(days=5) + eleven_days_ago = utcnow - timedelta(days=11) + far_past = utcnow - timedelta(days=1000) + event_data = {"test_attr": 5, "test_attr_10": "nice"} + + await hass.async_block_till_done() + await async_wait_recording_done(hass) + + def _insert_events(): + with session_scope(hass=hass) as session: + event_type_test_auto_purge = EventTypes(event_type="EVENT_TEST_AUTOPURGE") + event_type_test_purge = EventTypes(event_type="EVENT_TEST_PURGE") + event_type_test = EventTypes(event_type="EVENT_TEST") + event_type_unused = EventTypes(event_type="EVENT_TEST_UNUSED") + session.add_all( + ( + event_type_test_auto_purge, + event_type_test_purge, + event_type_test, + event_type_unused, + ) + ) + session.flush() + for _ in range(5): + for event_id in range(6): + if event_id < 2: + timestamp = eleven_days_ago + event_type = event_type_test_auto_purge + elif event_id < 4: + timestamp = five_days_ago + event_type = event_type_test_purge + else: + timestamp = utcnow + event_type = event_type_test + + session.add( + Events( + event_type=None, + event_type_id=event_type.event_type_id, + event_data=json_dumps(event_data), + origin="LOCAL", + time_fired_ts=dt_util.utc_to_timestamp(timestamp), + ) + ) + return instance.event_type_manager.get_many( + [ + "EVENT_TEST_AUTOPURGE", + "EVENT_TEST_PURGE", + "EVENT_TEST", + "EVENT_TEST_UNUSED", + ], + session, + ) + + event_type_to_id = await instance.async_add_executor_job(_insert_events) + test_event_type_ids = event_type_to_id.values() + with session_scope(hass=hass) as session: + events = session.query(Events).where( + Events.event_type_id.in_(test_event_type_ids) + ) + event_types = session.query(EventTypes).where( + EventTypes.event_type_id.in_(test_event_type_ids) + ) + + assert events.count() == 30 + assert event_types.count() == 4 + + # run purge_old_data() + finished = purge_old_data( + instance, + far_past, + repack=False, + ) + assert finished + assert events.count() == 30 + # We should remove the unused event type + assert event_types.count() == 3 + + assert "EVENT_TEST_UNUSED" not in instance.event_type_manager._id_map + + # we should only have 10 events left since + # only one event type was recorded now + finished = purge_old_data( + instance, + utcnow, + repack=False, + ) + assert finished + assert events.count() == 10 + assert event_types.count() == 1 + + # Purge everything + finished = purge_old_data( + instance, + utcnow + timedelta(seconds=1), + repack=False, + ) + assert finished + assert events.count() == 0 + assert event_types.count() == 0