3 from __future__
import annotations
5 from collections.abc
import Iterable, Mapping
7 from typing
import TYPE_CHECKING
9 from sqlalchemy
import MetaData, Table
10 from sqlalchemy.exc
import OperationalError
11 from sqlalchemy.orm
import DeclarativeBase
12 from sqlalchemy.orm.attributes
import InstrumentedAttribute
14 from ..const
import SupportedDialect
15 from ..db_schema
import DOUBLE_PRECISION_TYPE_SQL, DOUBLE_TYPE
16 from ..util
import session_scope
19 from ..
import Recorder
21 _LOGGER = logging.getLogger(__name__)
23 MYSQL_ERR_INCORRECT_STRING_VALUE = 1366
26 UTF8_NAME =
"๐๐"
29 PRECISE_NUMBER = 1.000000000000001
33 table_object: type[DeclarativeBase],
35 """Get the column names for the columns that need to be checked for precision."""
38 for column
in table_object.__table__.columns
39 if column.type
is DOUBLE_TYPE
45 table_object: type[DeclarativeBase],
46 columns: tuple[InstrumentedAttribute, ...],
48 """Do some basic checks for common schema errors caused by manual migration."""
49 schema_errors: set[str] = set()
51 if instance.dialect_name != SupportedDialect.MYSQL:
56 instance, table_object, columns
59 _LOGGER.exception(
"Error when validating DB schema")
67 table_object: type[DeclarativeBase],
69 """Verify the table has the correct collation."""
70 schema_errors: set[str] = set()
72 if instance.dialect_name != SupportedDialect.MYSQL:
77 instance, table_object
80 _LOGGER.exception(
"Error when validating DB schema")
88 table_object: type[DeclarativeBase],
90 """Ensure the table has the correct collation to avoid union errors with mixed collations."""
91 schema_errors: set[str] = set()
94 with session_scope(session=instance.get_session(), read_only=
True)
as session:
95 table = table_object.__tablename__
96 metadata_obj = MetaData()
97 reflected_table = Table(table, metadata_obj, autoload_with=instance.engine)
98 connection = session.connection()
99 dialect_kwargs = reflected_table.dialect_kwargs
104 dialect_kwargs.get(
"mysql_collate")
105 or dialect_kwargs.get(
"mariadb_collate")
106 or connection.dialect._fetch_setting(connection,
"collation_server")
108 if collate
and collate !=
"utf8mb4_unicode_ci":
110 "Database %s collation is not utf8mb4_unicode_ci",
113 schema_errors.add(f
"{table}.utf8mb4_unicode_ci")
119 table_object: type[DeclarativeBase],
120 columns: tuple[InstrumentedAttribute, ...],
122 """Do some basic checks for common schema errors caused by manual migration."""
123 schema_errors: set[str] = set()
126 with session_scope(session=instance.get_session(), read_only=
True)
as session:
127 db_object = table_object(**{column.key: UTF8_NAME
for column
in columns})
128 table = table_object.__tablename__
130 session.add(db_object)
133 except OperationalError
as err:
134 if err.orig
and err.orig.args[0] == MYSQL_ERR_INCORRECT_STRING_VALUE:
136 "Database %s statistics_meta does not support 4-byte UTF-8",
139 schema_errors.add(f
"{table}.4-byte UTF-8")
149 table_object: type[DeclarativeBase],
151 """Do some basic checks for common schema errors caused by manual migration."""
152 schema_errors: set[str] = set()
154 if instance.dialect_name
not in (
155 SupportedDialect.MYSQL,
156 SupportedDialect.POSTGRESQL,
162 _LOGGER.exception(
"Error when validating DB schema")
170 table_object: type[DeclarativeBase],
172 """Do some basic checks for common schema errors caused by manual migration."""
173 schema_errors: set[str] = set()
177 with session_scope(session=instance.get_session(), read_only=
True)
as session:
178 db_object = table_object(**{column: PRECISE_NUMBER
for column
in columns})
179 table = table_object.__tablename__
181 session.add(db_object)
183 session.refresh(db_object)
185 schema_errors=schema_errors,
186 stored={column: getattr(db_object, column)
for column
in columns},
187 expected={column: PRECISE_NUMBER
for column
in columns},
190 supports=
"double precision",
198 table_object: type[DeclarativeBase], schema_errors: set[str]
200 """Log schema errors."""
201 if not schema_errors:
204 "Detected %s schema errors: %s",
205 table_object.__tablename__,
206 ", ".join(sorted(schema_errors)),
211 schema_errors: set[str],
214 columns: Iterable[str],
218 """Check that the columns in the table support the given feature.
220 Errors are logged and added to the schema_errors set.
222 for column
in columns:
223 if stored[column] == expected[column]:
225 schema_errors.add(f
"{table_name}.{supports}")
227 "Column %s in database table %s does not support %s (stored=%s != expected=%s)",
237 instance: Recorder, table_object: type[DeclarativeBase], schema_errors: set[str]
239 """Correct utf8 issues detected by validate_db_schema."""
240 table_name = table_object.__tablename__
242 f
"{table_name}.4-byte UTF-8" in schema_errors
243 or f
"{table_name}.utf8mb4_unicode_ci" in schema_errors
245 from ..migration
import (
246 _correct_table_character_set_and_collation,
254 table_object: type[DeclarativeBase],
255 schema_errors: set[str],
257 """Correct precision issues detected by validate_db_schema."""
258 table_name = table_object.__tablename__
260 if f
"{table_name}.double precision" in schema_errors:
261 from ..migration
import (
267 session_maker = instance.get_session
268 engine = instance.engine
269 assert engine
is not None,
"Engine should be set"
274 [f
"{column} {DOUBLE_PRECISION_TYPE_SQL}" for column
in precision_columns],
set[str] _validate_table_schema_supports_utf8(Recorder instance, type[DeclarativeBase] table_object, tuple[InstrumentedAttribute,...] columns)
None _log_schema_errors(type[DeclarativeBase] table_object, set[str] schema_errors)
None correct_db_schema_precision(Recorder instance, type[DeclarativeBase] table_object, set[str] schema_errors)
None _check_columns(set[str] schema_errors, Mapping stored, Mapping expected, Iterable[str] columns, str table_name, str supports)
set[str] validate_table_schema_has_correct_collation(Recorder instance, type[DeclarativeBase] table_object)
None correct_db_schema_utf8(Recorder instance, type[DeclarativeBase] table_object, set[str] schema_errors)
set[str] _validate_table_schema_has_correct_collation(Recorder instance, type[DeclarativeBase] table_object)
set[str] validate_db_schema_precision(Recorder instance, type[DeclarativeBase] table_object)
set[str] _validate_db_schema_precision(Recorder instance, type[DeclarativeBase] table_object)
set[str] validate_table_schema_supports_utf8(Recorder instance, type[DeclarativeBase] table_object, tuple[InstrumentedAttribute,...] columns)
list[str] _get_precision_column_types(type[DeclarativeBase] table_object)
None _modify_columns(Callable[[], Session] session_maker, Engine engine, str table_name, list[str] columns_def)
None _correct_table_character_set_and_collation(str table, Callable[[], Session] session_maker)
Generator[Session] session_scope(*HomeAssistant|None hass=None, Session|None session=None, Callable[[Exception], bool]|None exception_filter=None, bool read_only=False)