This commit is contained in:
J. Nick Koston
2023-04-10 12:31:14 -10:00
parent 00c5d497fb
commit ae6d88ff1c
3 changed files with 31 additions and 34 deletions

View File

@@ -22,7 +22,6 @@ from sqlalchemy import (
from sqlalchemy.engine.row import Row from sqlalchemy.engine.row import Row
from sqlalchemy.orm.properties import MappedColumn from sqlalchemy.orm.properties import MappedColumn
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from sqlalchemy.sql.expression import literal
from homeassistant.const import COMPRESSED_STATE_LAST_UPDATED, COMPRESSED_STATE_STATE from homeassistant.const import COMPRESSED_STATE_LAST_UPDATED, COMPRESSED_STATE_STATE
from homeassistant.core import HomeAssistant, State, split_entity_id from homeassistant.core import HomeAssistant, State, split_entity_id
@@ -46,29 +45,24 @@ from .const import (
STATE_KEY, STATE_KEY,
) )
_QUERY_STATE_NO_ATTR_NO_LAST_CHANGED = (
States.metadata_id,
States.state,
States.last_updated_ts,
)
_QUERY_STATE_NO_ATTR = ( _QUERY_STATE_NO_ATTR = (
States.metadata_id, *_QUERY_STATE_NO_ATTR_NO_LAST_CHANGED,
States.state,
States.last_changed_ts, States.last_changed_ts,
States.last_updated_ts,
) )
_QUERY_STATE_NO_ATTR_NO_LAST_CHANGED = ( # type: ignore[var-annotated] _QUERY_ATTRIBUTES = (
States.metadata_id,
States.state,
literal(value=None).label("last_changed_ts"),
States.last_updated_ts,
)
_QUERY_STATES = (
*_QUERY_STATE_NO_ATTR,
# Remove States.attributes once all attributes are in StateAttributes.shared_attrs # Remove States.attributes once all attributes are in StateAttributes.shared_attrs
States.attributes, States.attributes,
StateAttributes.shared_attrs, StateAttributes.shared_attrs,
) )
_QUERY_STATES = (*_QUERY_STATE_NO_ATTR, *_QUERY_ATTRIBUTES)
_QUERY_STATES_NO_LAST_CHANGED = ( _QUERY_STATES_NO_LAST_CHANGED = (
*_QUERY_STATE_NO_ATTR_NO_LAST_CHANGED, *_QUERY_STATE_NO_ATTR_NO_LAST_CHANGED,
# Remove States.attributes once all attributes are in StateAttributes.shared_attrs *_QUERY_ATTRIBUTES,
States.attributes,
StateAttributes.shared_attrs,
) )
_FIELD_MAP = { _FIELD_MAP = {
cast(MappedColumn, field).name: idx cast(MappedColumn, field).name: idx
@@ -94,15 +88,16 @@ def _stmt_and_join_attributes(
def _select_from_subquery( def _select_from_subquery(
subquery: Subquery | CompoundSelect, no_attributes: bool subquery: Subquery | CompoundSelect, no_attributes: bool, include_last_changed: bool
) -> Select: ) -> Select:
"""Return the statement to select from the union.""" """Return the statement to select from the union."""
base_select = select( base_select = select(
subquery.c.metadata_id, subquery.c.metadata_id,
subquery.c.state, subquery.c.state,
subquery.c.last_changed_ts,
subquery.c.last_updated_ts, subquery.c.last_updated_ts,
) )
if include_last_changed:
base_select = base_select.add_columns(subquery.c.last_changed_ts)
if no_attributes: if no_attributes:
return base_select return base_select
return base_select.add_columns(subquery.c.attributes, subquery.c.shared_attrs) return base_select.add_columns(subquery.c.attributes, subquery.c.shared_attrs)
@@ -149,9 +144,8 @@ def _significant_states_stmt(
run_start_ts: float | None, run_start_ts: float | None,
) -> Select | CompoundSelect: ) -> Select | CompoundSelect:
"""Query the database for significant state changes.""" """Query the database for significant state changes."""
stmt = _stmt_and_join_attributes( include_last_changed = not significant_changes_only
no_attributes, include_last_changed=not significant_changes_only stmt = _stmt_and_join_attributes(no_attributes, include_last_changed)
)
if significant_changes_only: if significant_changes_only:
# Since we are filtering on entity_id (metadata_id) we can avoid # Since we are filtering on entity_id (metadata_id) we can avoid
# the join of the states_meta table since we already know which # the join of the states_meta table since we already know which
@@ -182,12 +176,15 @@ def _significant_states_stmt(
single_metadata_id, single_metadata_id,
metadata_ids, metadata_ids,
no_attributes, no_attributes,
include_last_changed,
).subquery(), ).subquery(),
no_attributes, no_attributes,
include_last_changed,
), ),
_select_from_subquery(stmt.subquery(), no_attributes), _select_from_subquery(stmt.subquery(), no_attributes, include_last_changed),
).subquery(), ).subquery(),
no_attributes, no_attributes,
include_last_changed,
) )
@@ -345,13 +342,15 @@ def _state_changed_during_period_stmt(
union_all( union_all(
_select_from_subquery( _select_from_subquery(
_get_single_entity_start_time_stmt( _get_single_entity_start_time_stmt(
start_time_ts, single_metadata_id, no_attributes start_time_ts, single_metadata_id, no_attributes, False
).subquery(), ).subquery(),
no_attributes, no_attributes,
False,
), ),
_select_from_subquery(stmt.subquery(), no_attributes), _select_from_subquery(stmt.subquery(), no_attributes, False),
).subquery(), ).subquery(),
no_attributes, no_attributes,
False,
) )
@@ -499,11 +498,12 @@ def _get_start_time_state_for_entities_stmt(
epoch_time: float, epoch_time: float,
metadata_ids: list[int], metadata_ids: list[int],
no_attributes: bool, no_attributes: bool,
include_last_changed: bool,
) -> Select: ) -> Select:
"""Baked query to get states for specific entities.""" """Baked query to get states for specific entities."""
# We got an include-list of entities, accelerate the query by filtering already # We got an include-list of entities, accelerate the query by filtering already
# in the inner query. # in the inner query.
stmt = _stmt_and_join_attributes(no_attributes, True).join( stmt = _stmt_and_join_attributes(no_attributes, include_last_changed).join(
( (
most_recent_states_for_entities_by_date := ( most_recent_states_for_entities_by_date := (
select( select(
@@ -555,32 +555,29 @@ def _get_start_time_state_stmt(
single_metadata_id: int | None, single_metadata_id: int | None,
metadata_ids: list[int], metadata_ids: list[int],
no_attributes: bool, no_attributes: bool,
include_last_changed: bool,
) -> Select: ) -> Select:
"""Return the states at a specific point in time.""" """Return the states at a specific point in time."""
if single_metadata_id: if single_metadata_id:
# Use an entirely different (and extremely fast) query if we only # Use an entirely different (and extremely fast) query if we only
# have a single entity id # have a single entity id
return _get_single_entity_start_time_stmt( return _get_single_entity_start_time_stmt(
epoch_time, epoch_time, single_metadata_id, no_attributes, include_last_changed
single_metadata_id,
no_attributes,
) )
# 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.
return _get_start_time_state_for_entities_stmt( return _get_start_time_state_for_entities_stmt(
run_start_ts, epoch_time, metadata_ids, no_attributes run_start_ts, epoch_time, metadata_ids, no_attributes, include_last_changed
) )
def _get_single_entity_start_time_stmt( def _get_single_entity_start_time_stmt(
epoch_time: float, epoch_time: float, metadata_id: int, no_attributes: bool, include_last_changed: bool
metadata_id: int,
no_attributes: bool,
) -> Select: ) -> Select:
# Use an entirely different (and extremely fast) query if we only # Use an entirely different (and extremely fast) query if we only
# have a single entity id # have a single entity id
stmt = ( stmt = (
_stmt_and_join_attributes(no_attributes, True) _stmt_and_join_attributes(no_attributes, include_last_changed)
.filter( .filter(
States.last_updated_ts < epoch_time, States.last_updated_ts < epoch_time,
States.metadata_id == metadata_id, States.metadata_id == metadata_id,

View File

@@ -63,7 +63,7 @@ class LazyState(State):
dt_util.utc_to_timestamp(start_time) if start_time else None dt_util.utc_to_timestamp(start_time) if start_time else None
) )
self._last_changed_ts: float | None = ( self._last_changed_ts: float | None = (
self._row.last_changed_ts or self._last_updated_ts getattr(self._row, "last_changed_ts", None) or self._last_updated_ts
) )
self._context: Context | None = None self._context: Context | None = None
self.attr_cache = attr_cache self.attr_cache = attr_cache

View File

@@ -912,7 +912,7 @@ def test_execute_stmt_lambda_element(
start_time_ts = dt_util.utcnow().timestamp() start_time_ts = dt_util.utcnow().timestamp()
stmt = lambda_stmt( stmt = lambda_stmt(
lambda: _get_single_entity_start_time_stmt( lambda: _get_single_entity_start_time_stmt(
start_time_ts, metadata_id, False start_time_ts, metadata_id, False, False
) )
) )
rows = util.execute_stmt_lambda_element(session, stmt) rows = util.execute_stmt_lambda_element(session, stmt)