"""Devices queries for logbook.""" from __future__ import annotations from collections.abc import Iterable import sqlalchemy from sqlalchemy import lambda_stmt, select from sqlalchemy.sql.elements import BooleanClauseList from sqlalchemy.sql.lambdas import StatementLambdaElement from sqlalchemy.sql.selectable import CTE, CompoundSelect, Select from homeassistant.components.recorder.db_schema import ( DEVICE_ID_IN_EVENT, EventData, Events, EventTypes, States, StatesMeta, ) from .common import ( apply_events_context_hints, apply_states_context_hints, select_events_context_id_subquery, select_events_context_only, select_events_without_states, select_states_context_only, ) def _select_device_id_context_ids_sub_query( start_day: float, end_day: float, event_type_ids: tuple[int, ...], json_quotable_device_ids: list[str], ) -> Select: """Generate a subquery to find context ids for multiple devices.""" inner = ( select_events_context_id_subquery(start_day, end_day, event_type_ids) .where(apply_event_device_id_matchers(json_quotable_device_ids)) .subquery() ) return select(inner.c.context_id_bin).group_by(inner.c.context_id_bin) def _apply_devices_context_union( sel: Select, start_day: float, end_day: float, event_type_ids: tuple[int, ...], json_quotable_device_ids: list[str], ) -> CompoundSelect: """Generate a CTE to find the device context ids and a query to find linked row.""" devices_cte: CTE = _select_device_id_context_ids_sub_query( start_day, end_day, event_type_ids, json_quotable_device_ids, ).cte() return sel.union_all( apply_events_context_hints( select_events_context_only() .select_from(devices_cte) .outerjoin(Events, devices_cte.c.context_id_bin == Events.context_id_bin) .outerjoin(EventTypes, (Events.event_type_id == EventTypes.event_type_id)) .outerjoin(EventData, (Events.data_id == EventData.data_id)), ), apply_states_context_hints( select_states_context_only() .select_from(devices_cte) .outerjoin(States, devices_cte.c.context_id_bin == States.context_id_bin) .outerjoin(StatesMeta, (States.metadata_id == StatesMeta.metadata_id)) ), ) def devices_stmt( start_day: float, end_day: float, event_type_ids: tuple[int, ...], json_quotable_device_ids: list[str], ) -> StatementLambdaElement: """Generate a logbook query for multiple devices.""" stmt = lambda_stmt( lambda: _apply_devices_context_union( select_events_without_states(start_day, end_day, event_type_ids).where( apply_event_device_id_matchers(json_quotable_device_ids) ), start_day, end_day, event_type_ids, json_quotable_device_ids, ).order_by(Events.time_fired_ts) ) return stmt def apply_event_device_id_matchers( json_quotable_device_ids: Iterable[str], ) -> BooleanClauseList: """Create matchers for the device_ids in the event_data.""" return DEVICE_ID_IN_EVENT.is_not(None) & sqlalchemy.cast( DEVICE_ID_IN_EVENT, sqlalchemy.Text() ).in_(json_quotable_device_ids)