hass-core/homeassistant/components/logbook/queries/devices.py
J. Nick Koston 8bd43760b6
Deduplicate event_types in the events table (#89465)
* Deduplicate event_types in the events table

* Deduplicate event_types in the events table

* more fixes

* adjust

* adjust

* fix product

* fix tests

* adjust

* migrate

* migrate

* migrate

* more test fixes

* more test fixes

* fix

* migration test

* adjust

* speed up

* fix index

* fix more tests

* handle db failure

* preload

* tweak

* adjust

* fix stale docs strings, remove dead code

* refactor

* fix slow tests

* coverage

* self join to resolve query performance

* fix typo

* no need for quiet

* no need to drop index already dropped

* remove index that will never be used

* drop index sooner as we no longer use it

* Revert "remove index that will never be used"

This reverts commit 461aad2c52.

* typo
2023-03-11 14:54:55 -05:00

102 lines
3.2 KiB
Python

"""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,
)
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_types: tuple[str, ...],
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_types)
.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_types: tuple[str, ...],
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_types,
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)
),
)
def devices_stmt(
start_day: float,
end_day: float,
event_types: tuple[str, ...],
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_types).where(
apply_event_device_id_matchers(json_quotable_device_ids)
),
start_day,
end_day,
event_types,
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)