This commit is contained in:
J. Nick Koston
2023-03-10 11:30:14 -10:00
parent 4232ababaf
commit 56fbef8357
4 changed files with 119 additions and 7 deletions

View File

@@ -239,7 +239,7 @@ class Events(Base):
"""Return string representation of instance for debugging.""" """Return string representation of instance for debugging."""
return ( return (
"<recorder.Events(" "<recorder.Events("
f"id={self.event_id}, type='{self.event_type}', " f"id={self.event_id}, event_type_id='{self.event_type_id}', "
f"origin_idx='{self.origin_idx}', time_fired='{self._time_fired_isotime}'" f"origin_idx='{self.origin_idx}', time_fired='{self._time_fired_isotime}'"
f", data_id={self.data_id})>" f", data_id={self.data_id})>"
) )

View File

@@ -571,10 +571,10 @@ def _purge_old_recorder_runs(
def _purge_old_event_types(instance: Recorder, session: Session) -> None: def _purge_old_event_types(instance: Recorder, session: Session) -> None:
"""Purge all old event types.""" """Purge all old event types."""
# Event types is small, no need to batch run it # Event types is small, no need to batch run it
purged_event_types = set() purge_event_types = set()
event_type_ids = set() event_type_ids = set()
for event_type_id, event_type in session.execute(find_event_types_to_purge()): 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) event_type_ids.add(event_type_id)
if not event_type_ids: 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) _LOGGER.debug("Deleted %s event types", deleted_rows)
# Evict any entries in the event_type cache referring to a purged state # 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: def _purge_filtered_data(instance: Recorder, session: Session) -> bool:

View File

@@ -743,9 +743,9 @@ def find_event_types_to_purge() -> StatementLambdaElement:
lambda: select(EventTypes.event_type_id, EventTypes.event_type).where( lambda: select(EventTypes.event_type_id, EventTypes.event_type).where(
EventTypes.event_type_id EventTypes.event_type_id
== ( == (
select( select(distinct(EventTypes.event_type_id).label("unused_event_type_id"))
distinct(Events.event_type_id).label("unused_event_type_id") .filter(EventTypes.event_type_id.not_in(select(Events.event_type_id)))
).filter(Events.event_type_id.not_in(select(EventTypes.event_type_id))) .subquery()
).c.unused_event_type_id ).c.unused_event_type_id
) )
) )

View File

@@ -16,6 +16,7 @@ from homeassistant.components.recorder.const import (
from homeassistant.components.recorder.db_schema import ( from homeassistant.components.recorder.db_schema import (
EventData, EventData,
Events, Events,
EventTypes,
RecorderRuns, RecorderRuns,
StateAttributes, StateAttributes,
States, States,
@@ -31,6 +32,7 @@ from homeassistant.components.recorder.tasks import PurgeTask
from homeassistant.components.recorder.util import session_scope from homeassistant.components.recorder.util import session_scope
from homeassistant.const import EVENT_STATE_CHANGED, EVENT_THEMES_UPDATED, STATE_ON from homeassistant.const import EVENT_STATE_CHANGED, EVENT_THEMES_UPDATED, STATE_ON
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.json import json_dumps
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util 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. # does not prevent future purges. Its ignored.
assert states_with_event_id.count() == 0 assert states_with_event_id.count() == 0
assert states_without_event_id.count() == 1 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