Remove recorder history queries for database schemas < 25 (#125649)

This commit is contained in:
Erik Montnemery 2024-09-10 12:43:08 +02:00 committed by GitHub
parent dcd7830a35
commit 99122fcb78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 5 additions and 531 deletions

View File

@ -169,19 +169,6 @@ def _lambda_stmt_and_join_attributes(
), ),
False, False,
) )
# 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 schema_version < 25:
if include_last_changed:
return (
lambda_stmt(lambda: select(*_QUERY_STATES_PRE_SCHEMA_25)),
False,
)
return (
lambda_stmt(lambda: select(*_QUERY_STATES_PRE_SCHEMA_25_NO_LAST_CHANGED)),
False,
)
if schema_version >= 31: if schema_version >= 31:
if include_last_changed: if include_last_changed:

View File

@ -5,30 +5,21 @@ from __future__ import annotations
from copy import copy from copy import copy
from datetime import datetime, timedelta from datetime import datetime, timedelta
import json import json
from unittest.mock import patch, sentinel from unittest.mock import sentinel
from freezegun import freeze_time from freezegun import freeze_time
import pytest import pytest
from sqlalchemy import text
from homeassistant.components import recorder from homeassistant.components import recorder
from homeassistant.components.recorder import Recorder, get_instance, history from homeassistant.components.recorder import Recorder, history
from homeassistant.components.recorder.db_schema import ( from homeassistant.components.recorder.db_schema import (
Events,
RecorderRuns,
StateAttributes, StateAttributes,
States, States,
StatesMeta, StatesMeta,
) )
from homeassistant.components.recorder.filters import Filters from homeassistant.components.recorder.filters import Filters
from homeassistant.components.recorder.history import legacy
from homeassistant.components.recorder.models import process_timestamp from homeassistant.components.recorder.models import process_timestamp
from homeassistant.components.recorder.models.legacy import (
LegacyLazyState,
LegacyLazyStatePreSchema31,
)
from homeassistant.components.recorder.util import session_scope from homeassistant.components.recorder.util import session_scope
import homeassistant.core as ha
from homeassistant.core import HomeAssistant, State from homeassistant.core import HomeAssistant, State
from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.json import JSONEncoder
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -57,77 +48,6 @@ def setup_recorder(recorder_mock: Recorder) -> recorder.Recorder:
"""Set up recorder.""" """Set up recorder."""
async def _async_get_states(
hass: HomeAssistant,
utc_point_in_time: datetime,
entity_ids: list[str] | None = None,
run: RecorderRuns | None = None,
no_attributes: bool = False,
):
"""Get states from the database."""
def _get_states_with_session():
with session_scope(hass=hass, read_only=True) as session:
attr_cache = {}
pre_31_schema = get_instance(hass).schema_version < 31
return [
LegacyLazyStatePreSchema31(row, attr_cache, None)
if pre_31_schema
else LegacyLazyState(
row,
attr_cache,
None,
row.entity_id,
)
for row in legacy._get_rows_with_session(
hass,
session,
utc_point_in_time,
entity_ids,
run,
no_attributes,
)
]
return await recorder.get_instance(hass).async_add_executor_job(
_get_states_with_session
)
def _add_db_entries(
hass: ha.HomeAssistant, point: datetime, entity_ids: list[str]
) -> None:
with session_scope(hass=hass) as session:
for idx, entity_id in enumerate(entity_ids):
session.add(
Events(
event_id=1001 + idx,
event_type="state_changed",
event_data="{}",
origin="LOCAL",
time_fired=point,
)
)
session.add(
States(
entity_id=entity_id,
state="on",
attributes='{"name":"the light"}',
last_changed=None,
last_updated=point,
event_id=1001 + idx,
attributes_id=1002 + idx,
)
)
session.add(
StateAttributes(
shared_attrs='{"name":"the shared light"}',
hash=1234 + idx,
attributes_id=1002 + idx,
)
)
async def test_get_full_significant_states_with_session_entity_no_matches( async def test_get_full_significant_states_with_session_entity_no_matches(
hass: HomeAssistant, hass: HomeAssistant,
) -> None: ) -> None:
@ -891,184 +811,6 @@ def record_states(
return zero, four, states return zero, four, states
@pytest.mark.skip_on_db_engine(["mysql", "postgresql"])
@pytest.mark.usefixtures("skip_by_db_engine")
async def test_state_changes_during_period_query_during_migration_to_schema_25(
hass: HomeAssistant,
recorder_db_url: str,
) -> None:
"""Test we can query data prior to schema 25 and during migration to schema 25.
This test doesn't run on MySQL / MariaDB / Postgresql; we can't drop the
state_attributes table.
"""
instance = recorder.get_instance(hass)
with patch.object(instance.states_meta_manager, "active", False):
start = dt_util.utcnow()
point = start + timedelta(seconds=1)
end = point + timedelta(seconds=1)
entity_id = "light.test"
await recorder.get_instance(hass).async_add_executor_job(
_add_db_entries, hass, point, [entity_id]
)
no_attributes = True
hist = history.state_changes_during_period(
hass, start, end, entity_id, no_attributes, include_start_time_state=False
)
state = hist[entity_id][0]
assert state.attributes == {}
no_attributes = False
hist = history.state_changes_during_period(
hass, start, end, entity_id, no_attributes, include_start_time_state=False
)
state = hist[entity_id][0]
assert state.attributes == {"name": "the shared light"}
with instance.engine.connect() as conn:
conn.execute(text("update states set attributes_id=NULL;"))
conn.execute(text("drop table state_attributes;"))
conn.commit()
with patch.object(instance, "schema_version", 24):
instance.states_meta_manager.active = False
no_attributes = True
hist = history.state_changes_during_period(
hass,
start,
end,
entity_id,
no_attributes,
include_start_time_state=False,
)
state = hist[entity_id][0]
assert state.attributes == {}
no_attributes = False
hist = history.state_changes_during_period(
hass,
start,
end,
entity_id,
no_attributes,
include_start_time_state=False,
)
state = hist[entity_id][0]
assert state.attributes == {"name": "the light"}
@pytest.mark.skip_on_db_engine(["mysql", "postgresql"])
@pytest.mark.usefixtures("skip_by_db_engine")
async def test_get_states_query_during_migration_to_schema_25(
hass: HomeAssistant,
recorder_db_url: str,
) -> None:
"""Test we can query data prior to schema 25 and during migration to schema 25.
This test doesn't run on MySQL / MariaDB / Postgresql; we can't drop the
state_attributes table.
"""
instance = recorder.get_instance(hass)
start = dt_util.utcnow()
point = start + timedelta(seconds=1)
end = point + timedelta(seconds=1)
entity_id = "light.test"
await instance.async_add_executor_job(_add_db_entries, hass, point, [entity_id])
assert instance.states_meta_manager.active
no_attributes = True
hist = await _async_get_states(hass, end, [entity_id], no_attributes=no_attributes)
state = hist[0]
assert state.attributes == {}
no_attributes = False
hist = await _async_get_states(hass, end, [entity_id], no_attributes=no_attributes)
state = hist[0]
assert state.attributes == {"name": "the shared light"}
with instance.engine.connect() as conn:
conn.execute(text("update states set attributes_id=NULL;"))
conn.execute(text("drop table state_attributes;"))
conn.commit()
with patch.object(instance, "schema_version", 24):
instance.states_meta_manager.active = False
no_attributes = True
hist = await _async_get_states(
hass, end, [entity_id], no_attributes=no_attributes
)
state = hist[0]
assert state.attributes == {}
no_attributes = False
hist = await _async_get_states(
hass, end, [entity_id], no_attributes=no_attributes
)
state = hist[0]
assert state.attributes == {"name": "the light"}
@pytest.mark.skip_on_db_engine(["mysql", "postgresql"])
@pytest.mark.usefixtures("skip_by_db_engine")
async def test_get_states_query_during_migration_to_schema_25_multiple_entities(
hass: HomeAssistant,
recorder_db_url: str,
) -> None:
"""Test we can query data prior to schema 25 and during migration to schema 25.
This test doesn't run on MySQL / MariaDB / Postgresql; we can't drop the
state_attributes table.
"""
instance = recorder.get_instance(hass)
start = dt_util.utcnow()
point = start + timedelta(seconds=1)
end = point + timedelta(seconds=1)
entity_id_1 = "light.test"
entity_id_2 = "switch.test"
entity_ids = [entity_id_1, entity_id_2]
await instance.async_add_executor_job(_add_db_entries, hass, point, entity_ids)
assert instance.states_meta_manager.active
no_attributes = True
hist = await _async_get_states(hass, end, entity_ids, no_attributes=no_attributes)
assert hist[0].attributes == {}
assert hist[1].attributes == {}
no_attributes = False
hist = await _async_get_states(hass, end, entity_ids, no_attributes=no_attributes)
assert hist[0].attributes == {"name": "the shared light"}
assert hist[1].attributes == {"name": "the shared light"}
with instance.engine.connect() as conn:
conn.execute(text("update states set attributes_id=NULL;"))
conn.execute(text("drop table state_attributes;"))
conn.commit()
with patch.object(instance, "schema_version", 24):
instance.states_meta_manager.active = False
no_attributes = True
hist = await _async_get_states(
hass, end, entity_ids, no_attributes=no_attributes
)
assert hist[0].attributes == {}
assert hist[1].attributes == {}
no_attributes = False
hist = await _async_get_states(
hass, end, entity_ids, no_attributes=no_attributes
)
assert hist[0].attributes == {"name": "the light"}
assert hist[1].attributes == {"name": "the light"}
async def test_get_full_significant_states_handles_empty_last_changed( async def test_get_full_significant_states_handles_empty_last_changed(
hass: HomeAssistant, hass: HomeAssistant,
) -> None: ) -> None:

View File

@ -5,21 +5,15 @@ from __future__ import annotations
from copy import copy from copy import copy
from datetime import datetime, timedelta from datetime import datetime, timedelta
import json import json
from unittest.mock import patch, sentinel from unittest.mock import sentinel
from freezegun import freeze_time from freezegun import freeze_time
import pytest import pytest
from sqlalchemy import text
from homeassistant.components import recorder from homeassistant.components import recorder
from homeassistant.components.recorder import Recorder, get_instance, history from homeassistant.components.recorder import Recorder, history
from homeassistant.components.recorder.filters import Filters from homeassistant.components.recorder.filters import Filters
from homeassistant.components.recorder.history import legacy
from homeassistant.components.recorder.models import process_timestamp from homeassistant.components.recorder.models import process_timestamp
from homeassistant.components.recorder.models.legacy import (
LegacyLazyState,
LegacyLazyStatePreSchema31,
)
from homeassistant.components.recorder.util import session_scope from homeassistant.components.recorder.util import session_scope
import homeassistant.core as ha import homeassistant.core as ha
from homeassistant.core import HomeAssistant, State from homeassistant.core import HomeAssistant, State
@ -35,7 +29,7 @@ from .common import (
async_wait_recording_done, async_wait_recording_done,
old_db_schema, old_db_schema,
) )
from .db_schema_42 import Events, RecorderRuns, StateAttributes, States, StatesMeta from .db_schema_42 import StateAttributes, States, StatesMeta
from tests.typing import RecorderInstanceGenerator from tests.typing import RecorderInstanceGenerator
@ -59,77 +53,6 @@ def setup_recorder(db_schema_42, recorder_mock: Recorder) -> recorder.Recorder:
"""Set up recorder.""" """Set up recorder."""
async def _async_get_states(
hass: HomeAssistant,
utc_point_in_time: datetime,
entity_ids: list[str] | None = None,
run: RecorderRuns | None = None,
no_attributes: bool = False,
):
"""Get states from the database."""
def _get_states_with_session():
with session_scope(hass=hass, read_only=True) as session:
attr_cache = {}
pre_31_schema = get_instance(hass).schema_version < 31
return [
LegacyLazyStatePreSchema31(row, attr_cache, None)
if pre_31_schema
else LegacyLazyState(
row,
attr_cache,
None,
row.entity_id,
)
for row in legacy._get_rows_with_session(
hass,
session,
utc_point_in_time,
entity_ids,
run,
no_attributes,
)
]
return await recorder.get_instance(hass).async_add_executor_job(
_get_states_with_session
)
def _add_db_entries(
hass: ha.HomeAssistant, point: datetime, entity_ids: list[str]
) -> None:
with session_scope(hass=hass) as session:
for idx, entity_id in enumerate(entity_ids):
session.add(
Events(
event_id=1001 + idx,
event_type="state_changed",
event_data="{}",
origin="LOCAL",
time_fired=point,
)
)
session.add(
States(
entity_id=entity_id,
state="on",
attributes='{"name":"the light"}',
last_changed=None,
last_updated=point,
event_id=1001 + idx,
attributes_id=1002 + idx,
)
)
session.add(
StateAttributes(
shared_attrs='{"name":"the shared light"}',
hash=1234 + idx,
attributes_id=1002 + idx,
)
)
async def test_get_full_significant_states_with_session_entity_no_matches( async def test_get_full_significant_states_with_session_entity_no_matches(
hass: HomeAssistant, hass: HomeAssistant,
) -> None: ) -> None:
@ -893,184 +816,6 @@ def record_states(
return zero, four, states return zero, four, states
@pytest.mark.skip_on_db_engine(["mysql", "postgresql"])
@pytest.mark.usefixtures("skip_by_db_engine")
async def test_state_changes_during_period_query_during_migration_to_schema_25(
hass: HomeAssistant,
recorder_db_url: str,
) -> None:
"""Test we can query data prior to schema 25 and during migration to schema 25.
This test doesn't run on MySQL / MariaDB / Postgresql; we can't drop the
state_attributes table.
"""
instance = recorder.get_instance(hass)
with patch.object(instance.states_meta_manager, "active", False):
start = dt_util.utcnow()
point = start + timedelta(seconds=1)
end = point + timedelta(seconds=1)
entity_id = "light.test"
await recorder.get_instance(hass).async_add_executor_job(
_add_db_entries, hass, point, [entity_id]
)
no_attributes = True
hist = history.state_changes_during_period(
hass, start, end, entity_id, no_attributes, include_start_time_state=False
)
state = hist[entity_id][0]
assert state.attributes == {}
no_attributes = False
hist = history.state_changes_during_period(
hass, start, end, entity_id, no_attributes, include_start_time_state=False
)
state = hist[entity_id][0]
assert state.attributes == {"name": "the shared light"}
with instance.engine.connect() as conn:
conn.execute(text("update states set attributes_id=NULL;"))
conn.execute(text("drop table state_attributes;"))
conn.commit()
with patch.object(instance, "schema_version", 24):
instance.states_meta_manager.active = False
no_attributes = True
hist = history.state_changes_during_period(
hass,
start,
end,
entity_id,
no_attributes,
include_start_time_state=False,
)
state = hist[entity_id][0]
assert state.attributes == {}
no_attributes = False
hist = history.state_changes_during_period(
hass,
start,
end,
entity_id,
no_attributes,
include_start_time_state=False,
)
state = hist[entity_id][0]
assert state.attributes == {"name": "the light"}
@pytest.mark.skip_on_db_engine(["mysql", "postgresql"])
@pytest.mark.usefixtures("skip_by_db_engine")
async def test_get_states_query_during_migration_to_schema_25(
hass: HomeAssistant,
recorder_db_url: str,
) -> None:
"""Test we can query data prior to schema 25 and during migration to schema 25.
This test doesn't run on MySQL / MariaDB / Postgresql; we can't drop the
state_attributes table.
"""
instance = recorder.get_instance(hass)
start = dt_util.utcnow()
point = start + timedelta(seconds=1)
end = point + timedelta(seconds=1)
entity_id = "light.test"
await instance.async_add_executor_job(_add_db_entries, hass, point, [entity_id])
assert instance.states_meta_manager.active
no_attributes = True
hist = await _async_get_states(hass, end, [entity_id], no_attributes=no_attributes)
state = hist[0]
assert state.attributes == {}
no_attributes = False
hist = await _async_get_states(hass, end, [entity_id], no_attributes=no_attributes)
state = hist[0]
assert state.attributes == {"name": "the shared light"}
with instance.engine.connect() as conn:
conn.execute(text("update states set attributes_id=NULL;"))
conn.execute(text("drop table state_attributes;"))
conn.commit()
with patch.object(instance, "schema_version", 24):
instance.states_meta_manager.active = False
no_attributes = True
hist = await _async_get_states(
hass, end, [entity_id], no_attributes=no_attributes
)
state = hist[0]
assert state.attributes == {}
no_attributes = False
hist = await _async_get_states(
hass, end, [entity_id], no_attributes=no_attributes
)
state = hist[0]
assert state.attributes == {"name": "the light"}
@pytest.mark.skip_on_db_engine(["mysql", "postgresql"])
@pytest.mark.usefixtures("skip_by_db_engine")
async def test_get_states_query_during_migration_to_schema_25_multiple_entities(
hass: HomeAssistant,
recorder_db_url: str,
) -> None:
"""Test we can query data prior to schema 25 and during migration to schema 25.
This test doesn't run on MySQL / MariaDB / Postgresql; we can't drop the
state_attributes table.
"""
instance = recorder.get_instance(hass)
start = dt_util.utcnow()
point = start + timedelta(seconds=1)
end = point + timedelta(seconds=1)
entity_id_1 = "light.test"
entity_id_2 = "switch.test"
entity_ids = [entity_id_1, entity_id_2]
await instance.async_add_executor_job(_add_db_entries, hass, point, entity_ids)
assert instance.states_meta_manager.active
no_attributes = True
hist = await _async_get_states(hass, end, entity_ids, no_attributes=no_attributes)
assert hist[0].attributes == {}
assert hist[1].attributes == {}
no_attributes = False
hist = await _async_get_states(hass, end, entity_ids, no_attributes=no_attributes)
assert hist[0].attributes == {"name": "the shared light"}
assert hist[1].attributes == {"name": "the shared light"}
with instance.engine.connect() as conn:
conn.execute(text("update states set attributes_id=NULL;"))
conn.execute(text("drop table state_attributes;"))
conn.commit()
with patch.object(instance, "schema_version", 24):
instance.states_meta_manager.active = False
no_attributes = True
hist = await _async_get_states(
hass, end, entity_ids, no_attributes=no_attributes
)
assert hist[0].attributes == {}
assert hist[1].attributes == {}
no_attributes = False
hist = await _async_get_states(
hass, end, entity_ids, no_attributes=no_attributes
)
assert hist[0].attributes == {"name": "the light"}
assert hist[1].attributes == {"name": "the light"}
async def test_get_full_significant_states_handles_empty_last_changed( async def test_get_full_significant_states_handles_empty_last_changed(
hass: HomeAssistant, hass: HomeAssistant,
) -> None: ) -> None: