diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 4af2a62151f..17909194793 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -12,50 +12,19 @@ _LOGGER = logging.getLogger(__name__) def purge_old_data(instance, purge_days, repack): """Purge events and states older than purge_days ago.""" from .models import States, Events - from sqlalchemy import func purge_before = dt_util.utcnow() - timedelta(days=purge_days) _LOGGER.debug("Purging events before %s", purge_before) with session_scope(session=instance.get_session()) as session: - delete_states = session.query(States) \ - .filter((States.last_updated < purge_before)) - - # For each entity, the most recent state is protected from deletion - # s.t. we can properly restore state even if the entity has not been - # updated in a long time - protected_states = session.query(func.max(States.state_id)) \ - .group_by(States.entity_id).all() - - protected_state_ids = tuple(state[0] for state in protected_states) - - if protected_state_ids: - delete_states = delete_states \ - .filter(~States.state_id.in_(protected_state_ids)) - - deleted_rows = delete_states.delete(synchronize_session=False) + deleted_rows = session.query(States) \ + .filter((States.last_updated < purge_before)) \ + .delete(synchronize_session=False) _LOGGER.debug("Deleted %s states", deleted_rows) - delete_events = session.query(Events) \ - .filter((Events.time_fired < purge_before)) - - # We also need to protect the events belonging to the protected states. - # Otherwise, if the SQL server has "ON DELETE CASCADE" as default, it - # will delete the protected state when deleting its associated - # event. Also, we would be producing NULLed foreign keys otherwise. - if protected_state_ids: - protected_events = session.query(States.event_id) \ - .filter(States.state_id.in_(protected_state_ids)) \ - .filter(States.event_id.isnot(None)) \ - .all() - - protected_event_ids = tuple(state[0] for state in protected_events) - - if protected_event_ids: - delete_events = delete_events \ - .filter(~Events.event_id.in_(protected_event_ids)) - - deleted_rows = delete_events.delete(synchronize_session=False) + deleted_rows = session.query(Events) \ + .filter((Events.time_fired < purge_before)) \ + .delete(synchronize_session=False) _LOGGER.debug("Deleted %s events", deleted_rows) # Execute sqlite vacuum command to free up space on disk diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 4249a8abfb9..7b03c7c3d8a 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -58,23 +58,6 @@ class TestRecorderPurge(unittest.TestCase): event_id=event_id + 1000 )) - # if self._add_test_events was called, we added a special event - # that should be protected from deletion, too - protected_event_id = getattr(self, "_protected_event_id", 2000) - - # add a state that is old but the only state of its entity and - # should be protected - session.add(States( - entity_id='test.rarely_updated_entity', - domain='sensor', - state='iamprotected', - attributes=json.dumps(attributes), - last_changed=eleven_days_ago, - last_updated=eleven_days_ago, - created=eleven_days_ago, - event_id=protected_event_id - )) - def _add_test_events(self): """Add a few events for testing.""" now = datetime.now() @@ -105,32 +88,19 @@ class TestRecorderPurge(unittest.TestCase): time_fired=timestamp, )) - # Add an event for the protected state - protected_event = Events( - event_type='EVENT_TEST_FOR_PROTECTED', - event_data=json.dumps(event_data), - origin='LOCAL', - created=eleven_days_ago, - time_fired=eleven_days_ago, - ) - session.add(protected_event) - session.flush() - - self._protected_event_id = protected_event.event_id - def test_purge_old_states(self): """Test deleting old states.""" self._add_test_states() - # make sure we start with 7 states + # make sure we start with 6 states with session_scope(hass=self.hass) as session: states = session.query(States) - assert states.count() == 7 + assert states.count() == 6 # run purge_old_data() purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) - # we should only have 3 states left after purging - assert states.count() == 3 + # we should only have 2 states left after purging + assert states.count() == 2 def test_purge_old_events(self): """Test deleting old events.""" @@ -139,12 +109,12 @@ class TestRecorderPurge(unittest.TestCase): with session_scope(hass=self.hass) as session: events = session.query(Events).filter( Events.event_type.like("EVENT_TEST%")) - assert events.count() == 7 + assert events.count() == 6 # run purge_old_data() purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) - # no state to protect, now we should only have 2 events left + # we should only have 2 events left assert events.count() == 2 def test_purge_method(self): @@ -156,11 +126,11 @@ class TestRecorderPurge(unittest.TestCase): # make sure we start with 6 states with session_scope(hass=self.hass) as session: states = session.query(States) - assert states.count() == 7 + assert states.count() == 6 events = session.query(Events).filter( Events.event_type.like("EVENT_TEST%")) - assert events.count() == 7 + assert events.count() == 6 self.hass.data[DATA_INSTANCE].block_till_done() @@ -172,8 +142,8 @@ class TestRecorderPurge(unittest.TestCase): self.hass.data[DATA_INSTANCE].block_till_done() # only purged old events - assert states.count() == 5 - assert events.count() == 5 + assert states.count() == 4 + assert events.count() == 4 # run purge method - correct service data self.hass.services.call('recorder', 'purge', @@ -183,19 +153,12 @@ class TestRecorderPurge(unittest.TestCase): # Small wait for recorder thread self.hass.data[DATA_INSTANCE].block_till_done() - # we should only have 3 states left after purging - assert states.count() == 3 + # we should only have 2 states left after purging + assert states.count() == 2 - # the protected state is among them - assert 'iamprotected' in ( - state.state for state in states) + # now we should only have 2 events left + assert events.count() == 2 - # now we should only have 3 events left - assert events.count() == 3 - - # and the protected event is among them - assert 'EVENT_TEST_FOR_PROTECTED' in ( - event.event_type for event in events.all()) assert not ('EVENT_TEST_PURGE' in ( event.event_type for event in events.all()))