# mypy: allow-untyped-defs, allow-untyped-calls, allow-incomplete-defs from __future__ import annotations import logging from typing import Optional from typing import TYPE_CHECKING from typing import Union from sqlalchemy import schema as sa_schema from ..autogenerate.compare.util import _InspectorConv from ..operations import ops from ..util import PriorityDispatchResult from ..util import sqla_compat if TYPE_CHECKING: from sqlalchemy.engine.interfaces import ReflectedCheckConstraint from sqlalchemy.sql.elements import quoted_name from sqlalchemy.sql.schema import CheckConstraint from sqlalchemy.sql.schema import Table from ..autogenerate.api import AutogenContext from ..ddl.impl import DefaultImpl from ..operations.ops import ModifyTableOps from ..runtime.plugins import Plugin log = logging.getLogger(__name__) def _make_check_constraint( impl: DefaultImpl, params: ReflectedCheckConstraint, conn_table: Table, ) -> CheckConstraint: const = sa_schema.CheckConstraint( params["sqltext"], name=params["name"], table=conn_table, **impl.adjust_reflected_dialect_options(params, "check_constraint"), ) return const # Current behaviour is kinda unfortunate # | Meta | Conn | Action | Note | # |----------|----------|--------------|-------------------------------| # | Named | Named | Compare | ok | # | Unnamed | Unnamed | Ignored | like sqlite | # | Unnamed | Named | See removal | like pg | # | Named | Unnamed | See addition | sqlite, name set after create | # | Named | Missing | See addition | ok | # | Unnamed | Missing | Ignored | not great | # | Missing | Named | See removal | ok | # | Missing | Unnamed | Ignored | not great | # def _compare_check_constraints( autogen_context: AutogenContext, modify_table_ops: ModifyTableOps, schema: Optional[str], tname: Union[quoted_name, str], conn_table: Optional[Table], metadata_table: Optional[Table], ) -> PriorityDispatchResult: if conn_table is None or metadata_table is None: return PriorityDispatchResult.CONTINUE inspector = autogen_context.inspector impl = autogen_context.migration_context.impl metadata_ck_constraints = sqla_compat.all_table_check_constraints( metadata_table ) try: conn_ck_list = _InspectorConv(inspector).get_check_constraints( tname, schema=schema ) except NotImplementedError: return PriorityDispatchResult.CONTINUE conn_ck_list = [ ck for ck in conn_ck_list if ck.get("name") is not None and autogen_context.run_name_filters( ck["name"], "check_constraint", {"table_name": tname, "schema_name": schema}, ) ] conn_ck_objs = { _make_check_constraint(impl, ck_def, conn_table) for ck_def in conn_ck_list } metadata_ck_sig = { impl._create_metadata_constraint_sig(ck) for ck in metadata_ck_constraints if sqla_compat._constraint_is_named(ck, autogen_context.dialect) } conn_ck_sig = { impl._create_reflected_constraint_sig(ck) for ck in conn_ck_objs } metadata_ck_by_name = { c.name: c for c in metadata_ck_sig if sqla_compat.constraint_name_string(c.name) } conn_ck_by_name = { c.name: c for c in conn_ck_sig if sqla_compat.constraint_name_string(c.name) } for removed_name in sorted( set(conn_ck_by_name).difference(metadata_ck_by_name) ): conn_obj = conn_ck_by_name[removed_name] if autogen_context.run_object_filters( conn_obj.const, conn_obj.name, "check_constraint", True, None, ): modify_table_ops.ops.append( ops.DropConstraintOp.from_constraint(conn_obj.const) ) log.info( "Detected removed check constraint %r on table %r", conn_obj.name, tname, ) for existing_name in sorted( set(metadata_ck_by_name).intersection(conn_ck_by_name) ): metadata_obj = metadata_ck_by_name[existing_name] conn_obj = conn_ck_by_name[existing_name] comparison = metadata_obj.compare_to_reflected(conn_obj) if comparison.is_different: if autogen_context.run_object_filters( metadata_obj.const, metadata_obj.name, "check_constraint", False, conn_obj.const, ): log.info( "Detected changed check constraint %r on table %r: %s", existing_name, tname, comparison.message, ) modify_table_ops.ops.append( ops.DropConstraintOp.from_constraint(conn_obj.const) ) modify_table_ops.ops.append( ops.AddConstraintOp.from_constraint(metadata_obj.const) ) elif comparison.is_skip: log.info( "Cannot compare check constraint %r, " "assuming equal and skipping. %s", existing_name, comparison.message, ) for added_name in sorted( set(metadata_ck_by_name).difference(conn_ck_by_name) ): metadata_obj = metadata_ck_by_name[added_name] if autogen_context.run_object_filters( metadata_obj.const, metadata_obj.name, "check_constraint", False, None, ): modify_table_ops.ops.append( ops.AddConstraintOp.from_constraint(metadata_obj.const) ) log.info( "Detected added check constraint %r on table %r", metadata_obj.name, tname, ) return PriorityDispatchResult.CONTINUE def setup(plugin: Plugin) -> None: plugin.add_autogenerate_comparator( _compare_check_constraints, "table", "checkconstraints", )