Guard against selecting all invalid entity_ids in history (#89929)

If all the entity_ids that were provided do not exist we would
end up passing an empty list of ids to the SQL query which
would do an unbounded select
pull/89941/head^2
J. Nick Koston 2023-03-19 16:03:12 -10:00 committed by GitHub
parent 5ffb233004
commit 7f3e4cb3af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 33 additions and 25 deletions

View File

@ -14,6 +14,7 @@ from homeassistant.components.recorder import get_instance
from homeassistant.components.recorder.filters import Filters from homeassistant.components.recorder.filters import Filters
from homeassistant.components.recorder.models import ( from homeassistant.components.recorder.models import (
bytes_to_uuid_hex_or_none, bytes_to_uuid_hex_or_none,
extract_metadata_ids,
process_datetime_to_timestamp, process_datetime_to_timestamp,
process_timestamp_to_utc_isoformat, process_timestamp_to_utc_isoformat,
) )
@ -154,14 +155,11 @@ class EventProcessor:
metadata_ids: list[int] | None = None metadata_ids: list[int] | None = None
if self.entity_ids: if self.entity_ids:
instance = get_instance(self.hass) instance = get_instance(self.hass)
entity_id_to_metadata_id = instance.states_meta_manager.get_many( metadata_ids = extract_metadata_ids(
self.entity_ids, session, False instance.states_meta_manager.get_many(
self.entity_ids, session, False
)
) )
metadata_ids = [
metadata_id
for metadata_id in entity_id_to_metadata_id.values()
if metadata_id is not None
]
stmt = statement_for_request( stmt = statement_for_request(
start_day, start_day,
end_day, end_day,

View File

@ -23,7 +23,12 @@ import homeassistant.util.dt as dt_util
from ... import recorder from ... import recorder
from ..db_schema import RecorderRuns, StateAttributes, States, StatesMeta from ..db_schema import RecorderRuns, StateAttributes, States, StatesMeta
from ..filters import Filters from ..filters import Filters
from ..models import LazyState, process_timestamp, row_to_compressed_state from ..models import (
LazyState,
extract_metadata_ids,
process_timestamp,
row_to_compressed_state,
)
from ..util import execute_stmt_lambda_element, session_scope from ..util import execute_stmt_lambda_element, session_scope
from .const import ( from .const import (
IGNORE_DOMAINS_ENTITY_ID_LIKE, IGNORE_DOMAINS_ENTITY_ID_LIKE,
@ -232,14 +237,12 @@ def get_significant_states_with_session(
entity_id_to_metadata_id: dict[str, int | None] | None = None entity_id_to_metadata_id: dict[str, int | None] | None = None
if entity_ids: if entity_ids:
instance = recorder.get_instance(hass) instance = recorder.get_instance(hass)
entity_id_to_metadata_id = instance.states_meta_manager.get_many( if not (
entity_ids, session, False entity_id_to_metadata_id := instance.states_meta_manager.get_many(
) entity_ids, session, False
metadata_ids = [ )
metadata_id ) or not (metadata_ids := extract_metadata_ids(entity_id_to_metadata_id)):
for metadata_id in entity_id_to_metadata_id.values() return {}
if metadata_id is not None
]
stmt = _significant_states_stmt( stmt = _significant_states_stmt(
start_time, start_time,
end_time, end_time,
@ -569,14 +572,9 @@ def _get_rows_with_session(
# We have more than one entity to look at so we need to do a query on states # We have more than one entity to look at so we need to do a query on states
# since the last recorder run started. # since the last recorder run started.
if entity_ids: if entity_ids:
if not entity_id_to_metadata_id: if not entity_id_to_metadata_id or not (
return [] metadata_ids := extract_metadata_ids(entity_id_to_metadata_id)
metadata_ids = [ ):
metadata_id
for metadata_id in entity_id_to_metadata_id.values()
if metadata_id is not None
]
if not metadata_ids:
return [] return []
stmt = _get_states_for_entities_stmt( stmt = _get_states_for_entities_stmt(
run.start, utc_point_in_time, metadata_ids, no_attributes run.start, utc_point_in_time, metadata_ids, no_attributes

View File

@ -8,7 +8,7 @@ from .context import (
uuid_hex_to_bytes_or_none, uuid_hex_to_bytes_or_none,
) )
from .database import DatabaseEngine, DatabaseOptimizer, UnsupportedDialect from .database import DatabaseEngine, DatabaseOptimizer, UnsupportedDialect
from .state import LazyState, row_to_compressed_state from .state import LazyState, extract_metadata_ids, row_to_compressed_state
from .statistics import ( from .statistics import (
CalendarStatisticPeriod, CalendarStatisticPeriod,
FixedStatisticPeriod, FixedStatisticPeriod,
@ -43,6 +43,7 @@ __all__ = [
"bytes_to_ulid_or_none", "bytes_to_ulid_or_none",
"bytes_to_uuid_hex_or_none", "bytes_to_uuid_hex_or_none",
"datetime_to_timestamp_or_none", "datetime_to_timestamp_or_none",
"extract_metadata_ids",
"process_datetime_to_timestamp", "process_datetime_to_timestamp",
"process_timestamp", "process_timestamp",
"process_timestamp_to_utc_isoformat", "process_timestamp_to_utc_isoformat",

View File

@ -24,6 +24,17 @@ from .time import process_timestamp
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def extract_metadata_ids(
entity_id_to_metadata_id: dict[str, int | None],
) -> list[int]:
"""Extract metadata ids from entity_id_to_metadata_id."""
return [
metadata_id
for metadata_id in entity_id_to_metadata_id.values()
if metadata_id is not None
]
class LazyState(State): class LazyState(State):
"""A lazy version of core State after schema 31.""" """A lazy version of core State after schema 31."""