Skip to content

Commit

Permalink
Merge 84a7fc9 into 9d4de2a
Browse files Browse the repository at this point in the history
  • Loading branch information
amelchio committed Dec 16, 2018
2 parents 9d4de2a + 84a7fc9 commit bd18d33
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 88 deletions.
43 changes: 6 additions & 37 deletions homeassistant/components/recorder/purge.py
Expand Up @@ -12,50 +12,19 @@
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
Expand Down
65 changes: 14 additions & 51 deletions tests/components/recorder/test_purge.py
Expand Up @@ -58,23 +58,6 @@ def _add_test_states(self):
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()
Expand Down Expand Up @@ -105,32 +88,19 @@ def _add_test_events(self):
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."""
Expand All @@ -139,12 +109,12 @@ def test_purge_old_events(self):
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):
Expand All @@ -156,11 +126,11 @@ def test_purge_method(self):
# 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()

Expand All @@ -172,8 +142,8 @@ def test_purge_method(self):
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',
Expand All @@ -183,19 +153,12 @@ def test_purge_method(self):
# 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

# the protected state is among them
assert 'iamprotected' in (
state.state for state in states)
# we should only have 2 states left after purging
assert states.count() == 2

# now we should only have 3 events left
assert events.count() == 3
# now we should only have 2 events left
assert events.count() == 2

# 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()))

Expand Down

0 comments on commit bd18d33

Please sign in to comment.