about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/alembic/ddl
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/alembic/ddl
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/alembic/ddl')
-rw-r--r--.venv/lib/python3.12/site-packages/alembic/ddl/__init__.py6
-rw-r--r--.venv/lib/python3.12/site-packages/alembic/ddl/_autogen.py329
-rw-r--r--.venv/lib/python3.12/site-packages/alembic/ddl/base.py336
-rw-r--r--.venv/lib/python3.12/site-packages/alembic/ddl/impl.py885
-rw-r--r--.venv/lib/python3.12/site-packages/alembic/ddl/mssql.py419
-rw-r--r--.venv/lib/python3.12/site-packages/alembic/ddl/mysql.py491
-rw-r--r--.venv/lib/python3.12/site-packages/alembic/ddl/oracle.py202
-rw-r--r--.venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py850
-rw-r--r--.venv/lib/python3.12/site-packages/alembic/ddl/sqlite.py237
9 files changed, 3755 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/alembic/ddl/__init__.py b/.venv/lib/python3.12/site-packages/alembic/ddl/__init__.py
new file mode 100644
index 00000000..f2f72b3d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/alembic/ddl/__init__.py
@@ -0,0 +1,6 @@
+from . import mssql
+from . import mysql
+from . import oracle
+from . import postgresql
+from . import sqlite
+from .impl import DefaultImpl as DefaultImpl
diff --git a/.venv/lib/python3.12/site-packages/alembic/ddl/_autogen.py b/.venv/lib/python3.12/site-packages/alembic/ddl/_autogen.py
new file mode 100644
index 00000000..74715b18
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/alembic/ddl/_autogen.py
@@ -0,0 +1,329 @@
+# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
+# mypy: no-warn-return-any, allow-any-generics
+
+from __future__ import annotations
+
+from typing import Any
+from typing import ClassVar
+from typing import Dict
+from typing import Generic
+from typing import NamedTuple
+from typing import Optional
+from typing import Sequence
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from sqlalchemy.sql.schema import Constraint
+from sqlalchemy.sql.schema import ForeignKeyConstraint
+from sqlalchemy.sql.schema import Index
+from sqlalchemy.sql.schema import UniqueConstraint
+from typing_extensions import TypeGuard
+
+from .. import util
+from ..util import sqla_compat
+
+if TYPE_CHECKING:
+    from typing import Literal
+
+    from alembic.autogenerate.api import AutogenContext
+    from alembic.ddl.impl import DefaultImpl
+
+CompareConstraintType = Union[Constraint, Index]
+
+_C = TypeVar("_C", bound=CompareConstraintType)
+
+_clsreg: Dict[str, Type[_constraint_sig]] = {}
+
+
+class ComparisonResult(NamedTuple):
+    status: Literal["equal", "different", "skip"]
+    message: str
+
+    @property
+    def is_equal(self) -> bool:
+        return self.status == "equal"
+
+    @property
+    def is_different(self) -> bool:
+        return self.status == "different"
+
+    @property
+    def is_skip(self) -> bool:
+        return self.status == "skip"
+
+    @classmethod
+    def Equal(cls) -> ComparisonResult:
+        """the constraints are equal."""
+        return cls("equal", "The two constraints are equal")
+
+    @classmethod
+    def Different(cls, reason: Union[str, Sequence[str]]) -> ComparisonResult:
+        """the constraints are different for the provided reason(s)."""
+        return cls("different", ", ".join(util.to_list(reason)))
+
+    @classmethod
+    def Skip(cls, reason: Union[str, Sequence[str]]) -> ComparisonResult:
+        """the constraint cannot be compared for the provided reason(s).
+
+        The message is logged, but the constraints will be otherwise
+        considered equal, meaning that no migration command will be
+        generated.
+        """
+        return cls("skip", ", ".join(util.to_list(reason)))
+
+
+class _constraint_sig(Generic[_C]):
+    const: _C
+
+    _sig: Tuple[Any, ...]
+    name: Optional[sqla_compat._ConstraintNameDefined]
+
+    impl: DefaultImpl
+
+    _is_index: ClassVar[bool] = False
+    _is_fk: ClassVar[bool] = False
+    _is_uq: ClassVar[bool] = False
+
+    _is_metadata: bool
+
+    def __init_subclass__(cls) -> None:
+        cls._register()
+
+    @classmethod
+    def _register(cls):
+        raise NotImplementedError()
+
+    def __init__(
+        self, is_metadata: bool, impl: DefaultImpl, const: _C
+    ) -> None:
+        raise NotImplementedError()
+
+    def compare_to_reflected(
+        self, other: _constraint_sig[Any]
+    ) -> ComparisonResult:
+        assert self.impl is other.impl
+        assert self._is_metadata
+        assert not other._is_metadata
+
+        return self._compare_to_reflected(other)
+
+    def _compare_to_reflected(
+        self, other: _constraint_sig[_C]
+    ) -> ComparisonResult:
+        raise NotImplementedError()
+
+    @classmethod
+    def from_constraint(
+        cls, is_metadata: bool, impl: DefaultImpl, constraint: _C
+    ) -> _constraint_sig[_C]:
+        # these could be cached by constraint/impl, however, if the
+        # constraint is modified in place, then the sig is wrong.  the mysql
+        # impl currently does this, and if we fixed that we can't be sure
+        # someone else might do it too, so play it safe.
+        sig = _clsreg[constraint.__visit_name__](is_metadata, impl, constraint)
+        return sig
+
+    def md_name_to_sql_name(self, context: AutogenContext) -> Optional[str]:
+        return sqla_compat._get_constraint_final_name(
+            self.const, context.dialect
+        )
+
+    @util.memoized_property
+    def is_named(self):
+        return sqla_compat._constraint_is_named(self.const, self.impl.dialect)
+
+    @util.memoized_property
+    def unnamed(self) -> Tuple[Any, ...]:
+        return self._sig
+
+    @util.memoized_property
+    def unnamed_no_options(self) -> Tuple[Any, ...]:
+        raise NotImplementedError()
+
+    @util.memoized_property
+    def _full_sig(self) -> Tuple[Any, ...]:
+        return (self.name,) + self.unnamed
+
+    def __eq__(self, other) -> bool:
+        return self._full_sig == other._full_sig
+
+    def __ne__(self, other) -> bool:
+        return self._full_sig != other._full_sig
+
+    def __hash__(self) -> int:
+        return hash(self._full_sig)
+
+
+class _uq_constraint_sig(_constraint_sig[UniqueConstraint]):
+    _is_uq = True
+
+    @classmethod
+    def _register(cls) -> None:
+        _clsreg["unique_constraint"] = cls
+
+    is_unique = True
+
+    def __init__(
+        self,
+        is_metadata: bool,
+        impl: DefaultImpl,
+        const: UniqueConstraint,
+    ) -> None:
+        self.impl = impl
+        self.const = const
+        self.name = sqla_compat.constraint_name_or_none(const.name)
+        self._sig = tuple(sorted([col.name for col in const.columns]))
+        self._is_metadata = is_metadata
+
+    @property
+    def column_names(self) -> Tuple[str, ...]:
+        return tuple([col.name for col in self.const.columns])
+
+    def _compare_to_reflected(
+        self, other: _constraint_sig[_C]
+    ) -> ComparisonResult:
+        assert self._is_metadata
+        metadata_obj = self
+        conn_obj = other
+
+        assert is_uq_sig(conn_obj)
+        return self.impl.compare_unique_constraint(
+            metadata_obj.const, conn_obj.const
+        )
+
+
+class _ix_constraint_sig(_constraint_sig[Index]):
+    _is_index = True
+
+    name: sqla_compat._ConstraintName
+
+    @classmethod
+    def _register(cls) -> None:
+        _clsreg["index"] = cls
+
+    def __init__(
+        self, is_metadata: bool, impl: DefaultImpl, const: Index
+    ) -> None:
+        self.impl = impl
+        self.const = const
+        self.name = const.name
+        self.is_unique = bool(const.unique)
+        self._is_metadata = is_metadata
+
+    def _compare_to_reflected(
+        self, other: _constraint_sig[_C]
+    ) -> ComparisonResult:
+        assert self._is_metadata
+        metadata_obj = self
+        conn_obj = other
+
+        assert is_index_sig(conn_obj)
+        return self.impl.compare_indexes(metadata_obj.const, conn_obj.const)
+
+    @util.memoized_property
+    def has_expressions(self):
+        return sqla_compat.is_expression_index(self.const)
+
+    @util.memoized_property
+    def column_names(self) -> Tuple[str, ...]:
+        return tuple([col.name for col in self.const.columns])
+
+    @util.memoized_property
+    def column_names_optional(self) -> Tuple[Optional[str], ...]:
+        return tuple(
+            [getattr(col, "name", None) for col in self.const.expressions]
+        )
+
+    @util.memoized_property
+    def is_named(self):
+        return True
+
+    @util.memoized_property
+    def unnamed(self):
+        return (self.is_unique,) + self.column_names_optional
+
+
+class _fk_constraint_sig(_constraint_sig[ForeignKeyConstraint]):
+    _is_fk = True
+
+    @classmethod
+    def _register(cls) -> None:
+        _clsreg["foreign_key_constraint"] = cls
+
+    def __init__(
+        self,
+        is_metadata: bool,
+        impl: DefaultImpl,
+        const: ForeignKeyConstraint,
+    ) -> None:
+        self._is_metadata = is_metadata
+
+        self.impl = impl
+        self.const = const
+
+        self.name = sqla_compat.constraint_name_or_none(const.name)
+
+        (
+            self.source_schema,
+            self.source_table,
+            self.source_columns,
+            self.target_schema,
+            self.target_table,
+            self.target_columns,
+            onupdate,
+            ondelete,
+            deferrable,
+            initially,
+        ) = sqla_compat._fk_spec(const)
+
+        self._sig: Tuple[Any, ...] = (
+            self.source_schema,
+            self.source_table,
+            tuple(self.source_columns),
+            self.target_schema,
+            self.target_table,
+            tuple(self.target_columns),
+        ) + (
+            (
+                (None if onupdate.lower() == "no action" else onupdate.lower())
+                if onupdate
+                else None
+            ),
+            (
+                (None if ondelete.lower() == "no action" else ondelete.lower())
+                if ondelete
+                else None
+            ),
+            # convert initially + deferrable into one three-state value
+            (
+                "initially_deferrable"
+                if initially and initially.lower() == "deferred"
+                else "deferrable" if deferrable else "not deferrable"
+            ),
+        )
+
+    @util.memoized_property
+    def unnamed_no_options(self):
+        return (
+            self.source_schema,
+            self.source_table,
+            tuple(self.source_columns),
+            self.target_schema,
+            self.target_table,
+            tuple(self.target_columns),
+        )
+
+
+def is_index_sig(sig: _constraint_sig) -> TypeGuard[_ix_constraint_sig]:
+    return sig._is_index
+
+
+def is_uq_sig(sig: _constraint_sig) -> TypeGuard[_uq_constraint_sig]:
+    return sig._is_uq
+
+
+def is_fk_sig(sig: _constraint_sig) -> TypeGuard[_fk_constraint_sig]:
+    return sig._is_fk
diff --git a/.venv/lib/python3.12/site-packages/alembic/ddl/base.py b/.venv/lib/python3.12/site-packages/alembic/ddl/base.py
new file mode 100644
index 00000000..bd55c56d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/alembic/ddl/base.py
@@ -0,0 +1,336 @@
+# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
+# mypy: no-warn-return-any, allow-any-generics
+
+from __future__ import annotations
+
+import functools
+from typing import Optional
+from typing import TYPE_CHECKING
+from typing import Union
+
+from sqlalchemy import exc
+from sqlalchemy import Integer
+from sqlalchemy import types as sqltypes
+from sqlalchemy.ext.compiler import compiles
+from sqlalchemy.schema import Column
+from sqlalchemy.schema import DDLElement
+from sqlalchemy.sql.elements import quoted_name
+
+from ..util.sqla_compat import _columns_for_constraint  # noqa
+from ..util.sqla_compat import _find_columns  # noqa
+from ..util.sqla_compat import _fk_spec  # noqa
+from ..util.sqla_compat import _is_type_bound  # noqa
+from ..util.sqla_compat import _table_for_constraint  # noqa
+
+if TYPE_CHECKING:
+    from typing import Any
+
+    from sqlalchemy import Computed
+    from sqlalchemy import Identity
+    from sqlalchemy.sql.compiler import Compiled
+    from sqlalchemy.sql.compiler import DDLCompiler
+    from sqlalchemy.sql.elements import TextClause
+    from sqlalchemy.sql.functions import Function
+    from sqlalchemy.sql.schema import FetchedValue
+    from sqlalchemy.sql.type_api import TypeEngine
+
+    from .impl import DefaultImpl
+
+_ServerDefault = Union["TextClause", "FetchedValue", "Function[Any]", str]
+
+
+class AlterTable(DDLElement):
+    """Represent an ALTER TABLE statement.
+
+    Only the string name and optional schema name of the table
+    is required, not a full Table object.
+
+    """
+
+    def __init__(
+        self,
+        table_name: str,
+        schema: Optional[Union[quoted_name, str]] = None,
+    ) -> None:
+        self.table_name = table_name
+        self.schema = schema
+
+
+class RenameTable(AlterTable):
+    def __init__(
+        self,
+        old_table_name: str,
+        new_table_name: Union[quoted_name, str],
+        schema: Optional[Union[quoted_name, str]] = None,
+    ) -> None:
+        super().__init__(old_table_name, schema=schema)
+        self.new_table_name = new_table_name
+
+
+class AlterColumn(AlterTable):
+    def __init__(
+        self,
+        name: str,
+        column_name: str,
+        schema: Optional[str] = None,
+        existing_type: Optional[TypeEngine] = None,
+        existing_nullable: Optional[bool] = None,
+        existing_server_default: Optional[_ServerDefault] = None,
+        existing_comment: Optional[str] = None,
+    ) -> None:
+        super().__init__(name, schema=schema)
+        self.column_name = column_name
+        self.existing_type = (
+            sqltypes.to_instance(existing_type)
+            if existing_type is not None
+            else None
+        )
+        self.existing_nullable = existing_nullable
+        self.existing_server_default = existing_server_default
+        self.existing_comment = existing_comment
+
+
+class ColumnNullable(AlterColumn):
+    def __init__(
+        self, name: str, column_name: str, nullable: bool, **kw
+    ) -> None:
+        super().__init__(name, column_name, **kw)
+        self.nullable = nullable
+
+
+class ColumnType(AlterColumn):
+    def __init__(
+        self, name: str, column_name: str, type_: TypeEngine, **kw
+    ) -> None:
+        super().__init__(name, column_name, **kw)
+        self.type_ = sqltypes.to_instance(type_)
+
+
+class ColumnName(AlterColumn):
+    def __init__(
+        self, name: str, column_name: str, newname: str, **kw
+    ) -> None:
+        super().__init__(name, column_name, **kw)
+        self.newname = newname
+
+
+class ColumnDefault(AlterColumn):
+    def __init__(
+        self,
+        name: str,
+        column_name: str,
+        default: Optional[_ServerDefault],
+        **kw,
+    ) -> None:
+        super().__init__(name, column_name, **kw)
+        self.default = default
+
+
+class ComputedColumnDefault(AlterColumn):
+    def __init__(
+        self, name: str, column_name: str, default: Optional[Computed], **kw
+    ) -> None:
+        super().__init__(name, column_name, **kw)
+        self.default = default
+
+
+class IdentityColumnDefault(AlterColumn):
+    def __init__(
+        self,
+        name: str,
+        column_name: str,
+        default: Optional[Identity],
+        impl: DefaultImpl,
+        **kw,
+    ) -> None:
+        super().__init__(name, column_name, **kw)
+        self.default = default
+        self.impl = impl
+
+
+class AddColumn(AlterTable):
+    def __init__(
+        self,
+        name: str,
+        column: Column[Any],
+        schema: Optional[Union[quoted_name, str]] = None,
+    ) -> None:
+        super().__init__(name, schema=schema)
+        self.column = column
+
+
+class DropColumn(AlterTable):
+    def __init__(
+        self, name: str, column: Column[Any], schema: Optional[str] = None
+    ) -> None:
+        super().__init__(name, schema=schema)
+        self.column = column
+
+
+class ColumnComment(AlterColumn):
+    def __init__(
+        self, name: str, column_name: str, comment: Optional[str], **kw
+    ) -> None:
+        super().__init__(name, column_name, **kw)
+        self.comment = comment
+
+
+@compiles(RenameTable)
+def visit_rename_table(
+    element: RenameTable, compiler: DDLCompiler, **kw
+) -> str:
+    return "%s RENAME TO %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_table_name(compiler, element.new_table_name, element.schema),
+    )
+
+
+@compiles(AddColumn)
+def visit_add_column(element: AddColumn, compiler: DDLCompiler, **kw) -> str:
+    return "%s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        add_column(compiler, element.column, **kw),
+    )
+
+
+@compiles(DropColumn)
+def visit_drop_column(element: DropColumn, compiler: DDLCompiler, **kw) -> str:
+    return "%s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        drop_column(compiler, element.column.name, **kw),
+    )
+
+
+@compiles(ColumnNullable)
+def visit_column_nullable(
+    element: ColumnNullable, compiler: DDLCompiler, **kw
+) -> str:
+    return "%s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        "DROP NOT NULL" if element.nullable else "SET NOT NULL",
+    )
+
+
+@compiles(ColumnType)
+def visit_column_type(element: ColumnType, compiler: DDLCompiler, **kw) -> str:
+    return "%s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        "TYPE %s" % format_type(compiler, element.type_),
+    )
+
+
+@compiles(ColumnName)
+def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str:
+    return "%s RENAME %s TO %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_column_name(compiler, element.column_name),
+        format_column_name(compiler, element.newname),
+    )
+
+
+@compiles(ColumnDefault)
+def visit_column_default(
+    element: ColumnDefault, compiler: DDLCompiler, **kw
+) -> str:
+    return "%s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        (
+            "SET DEFAULT %s" % format_server_default(compiler, element.default)
+            if element.default is not None
+            else "DROP DEFAULT"
+        ),
+    )
+
+
+@compiles(ComputedColumnDefault)
+def visit_computed_column(
+    element: ComputedColumnDefault, compiler: DDLCompiler, **kw
+):
+    raise exc.CompileError(
+        'Adding or removing a "computed" construct, e.g. GENERATED '
+        "ALWAYS AS, to or from an existing column is not supported."
+    )
+
+
+@compiles(IdentityColumnDefault)
+def visit_identity_column(
+    element: IdentityColumnDefault, compiler: DDLCompiler, **kw
+):
+    raise exc.CompileError(
+        'Adding, removing or modifying an "identity" construct, '
+        "e.g. GENERATED AS IDENTITY, to or from an existing "
+        "column is not supported in this dialect."
+    )
+
+
+def quote_dotted(
+    name: Union[quoted_name, str], quote: functools.partial
+) -> Union[quoted_name, str]:
+    """quote the elements of a dotted name"""
+
+    if isinstance(name, quoted_name):
+        return quote(name)
+    result = ".".join([quote(x) for x in name.split(".")])
+    return result
+
+
+def format_table_name(
+    compiler: Compiled,
+    name: Union[quoted_name, str],
+    schema: Optional[Union[quoted_name, str]],
+) -> Union[quoted_name, str]:
+    quote = functools.partial(compiler.preparer.quote)
+    if schema:
+        return quote_dotted(schema, quote) + "." + quote(name)
+    else:
+        return quote(name)
+
+
+def format_column_name(
+    compiler: DDLCompiler, name: Optional[Union[quoted_name, str]]
+) -> Union[quoted_name, str]:
+    return compiler.preparer.quote(name)  # type: ignore[arg-type]
+
+
+def format_server_default(
+    compiler: DDLCompiler,
+    default: Optional[_ServerDefault],
+) -> str:
+    return compiler.get_column_default_string(
+        Column("x", Integer, server_default=default)
+    )
+
+
+def format_type(compiler: DDLCompiler, type_: TypeEngine) -> str:
+    return compiler.dialect.type_compiler.process(type_)
+
+
+def alter_table(
+    compiler: DDLCompiler,
+    name: str,
+    schema: Optional[str],
+) -> str:
+    return "ALTER TABLE %s" % format_table_name(compiler, name, schema)
+
+
+def drop_column(compiler: DDLCompiler, name: str, **kw) -> str:
+    return "DROP COLUMN %s" % format_column_name(compiler, name)
+
+
+def alter_column(compiler: DDLCompiler, name: str) -> str:
+    return "ALTER COLUMN %s" % format_column_name(compiler, name)
+
+
+def add_column(compiler: DDLCompiler, column: Column[Any], **kw) -> str:
+    text = "ADD COLUMN %s" % compiler.get_column_specification(column, **kw)
+
+    const = " ".join(
+        compiler.process(constraint) for constraint in column.constraints
+    )
+    if const:
+        text += " " + const
+
+    return text
diff --git a/.venv/lib/python3.12/site-packages/alembic/ddl/impl.py b/.venv/lib/python3.12/site-packages/alembic/ddl/impl.py
new file mode 100644
index 00000000..c116fcfa
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/alembic/ddl/impl.py
@@ -0,0 +1,885 @@
+# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
+# mypy: no-warn-return-any, allow-any-generics
+
+from __future__ import annotations
+
+import logging
+import re
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import Iterable
+from typing import List
+from typing import Mapping
+from typing import NamedTuple
+from typing import Optional
+from typing import Sequence
+from typing import Set
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import Union
+
+from sqlalchemy import cast
+from sqlalchemy import Column
+from sqlalchemy import MetaData
+from sqlalchemy import PrimaryKeyConstraint
+from sqlalchemy import schema
+from sqlalchemy import String
+from sqlalchemy import Table
+from sqlalchemy import text
+
+from . import _autogen
+from . import base
+from ._autogen import _constraint_sig as _constraint_sig
+from ._autogen import ComparisonResult as ComparisonResult
+from .. import util
+from ..util import sqla_compat
+
+if TYPE_CHECKING:
+    from typing import Literal
+    from typing import TextIO
+
+    from sqlalchemy.engine import Connection
+    from sqlalchemy.engine import Dialect
+    from sqlalchemy.engine.cursor import CursorResult
+    from sqlalchemy.engine.reflection import Inspector
+    from sqlalchemy.sql import ClauseElement
+    from sqlalchemy.sql import Executable
+    from sqlalchemy.sql.elements import ColumnElement
+    from sqlalchemy.sql.elements import quoted_name
+    from sqlalchemy.sql.schema import Constraint
+    from sqlalchemy.sql.schema import ForeignKeyConstraint
+    from sqlalchemy.sql.schema import Index
+    from sqlalchemy.sql.schema import UniqueConstraint
+    from sqlalchemy.sql.selectable import TableClause
+    from sqlalchemy.sql.type_api import TypeEngine
+
+    from .base import _ServerDefault
+    from ..autogenerate.api import AutogenContext
+    from ..operations.batch import ApplyBatchImpl
+    from ..operations.batch import BatchOperationsImpl
+
+log = logging.getLogger(__name__)
+
+
+class ImplMeta(type):
+    def __init__(
+        cls,
+        classname: str,
+        bases: Tuple[Type[DefaultImpl]],
+        dict_: Dict[str, Any],
+    ):
+        newtype = type.__init__(cls, classname, bases, dict_)
+        if "__dialect__" in dict_:
+            _impls[dict_["__dialect__"]] = cls  # type: ignore[assignment]
+        return newtype
+
+
+_impls: Dict[str, Type[DefaultImpl]] = {}
+
+
+class DefaultImpl(metaclass=ImplMeta):
+    """Provide the entrypoint for major migration operations,
+    including database-specific behavioral variances.
+
+    While individual SQL/DDL constructs already provide
+    for database-specific implementations, variances here
+    allow for entirely different sequences of operations
+    to take place for a particular migration, such as
+    SQL Server's special 'IDENTITY INSERT' step for
+    bulk inserts.
+
+    """
+
+    __dialect__ = "default"
+
+    transactional_ddl = False
+    command_terminator = ";"
+    type_synonyms: Tuple[Set[str], ...] = ({"NUMERIC", "DECIMAL"},)
+    type_arg_extract: Sequence[str] = ()
+    # These attributes are deprecated in SQLAlchemy via #10247. They need to
+    # be ignored to support older version that did not use dialect kwargs.
+    # They only apply to Oracle and are replaced by oracle_order,
+    # oracle_on_null
+    identity_attrs_ignore: Tuple[str, ...] = ("order", "on_null")
+
+    def __init__(
+        self,
+        dialect: Dialect,
+        connection: Optional[Connection],
+        as_sql: bool,
+        transactional_ddl: Optional[bool],
+        output_buffer: Optional[TextIO],
+        context_opts: Dict[str, Any],
+    ) -> None:
+        self.dialect = dialect
+        self.connection = connection
+        self.as_sql = as_sql
+        self.literal_binds = context_opts.get("literal_binds", False)
+
+        self.output_buffer = output_buffer
+        self.memo: dict = {}
+        self.context_opts = context_opts
+        if transactional_ddl is not None:
+            self.transactional_ddl = transactional_ddl
+
+        if self.literal_binds:
+            if not self.as_sql:
+                raise util.CommandError(
+                    "Can't use literal_binds setting without as_sql mode"
+                )
+
+    @classmethod
+    def get_by_dialect(cls, dialect: Dialect) -> Type[DefaultImpl]:
+        return _impls[dialect.name]
+
+    def static_output(self, text: str) -> None:
+        assert self.output_buffer is not None
+        self.output_buffer.write(text + "\n\n")
+        self.output_buffer.flush()
+
+    def version_table_impl(
+        self,
+        *,
+        version_table: str,
+        version_table_schema: Optional[str],
+        version_table_pk: bool,
+        **kw: Any,
+    ) -> Table:
+        """Generate a :class:`.Table` object which will be used as the
+        structure for the Alembic version table.
+
+        Third party dialects may override this hook to provide an alternate
+        structure for this :class:`.Table`; requirements are only that it
+        be named based on the ``version_table`` parameter and contains
+        at least a single string-holding column named ``version_num``.
+
+        .. versionadded:: 1.14
+
+        """
+        vt = Table(
+            version_table,
+            MetaData(),
+            Column("version_num", String(32), nullable=False),
+            schema=version_table_schema,
+        )
+        if version_table_pk:
+            vt.append_constraint(
+                PrimaryKeyConstraint(
+                    "version_num", name=f"{version_table}_pkc"
+                )
+            )
+
+        return vt
+
+    def requires_recreate_in_batch(
+        self, batch_op: BatchOperationsImpl
+    ) -> bool:
+        """Return True if the given :class:`.BatchOperationsImpl`
+        would need the table to be recreated and copied in order to
+        proceed.
+
+        Normally, only returns True on SQLite when operations other
+        than add_column are present.
+
+        """
+        return False
+
+    def prep_table_for_batch(
+        self, batch_impl: ApplyBatchImpl, table: Table
+    ) -> None:
+        """perform any operations needed on a table before a new
+        one is created to replace it in batch mode.
+
+        the PG dialect uses this to drop constraints on the table
+        before the new one uses those same names.
+
+        """
+
+    @property
+    def bind(self) -> Optional[Connection]:
+        return self.connection
+
+    def _exec(
+        self,
+        construct: Union[Executable, str],
+        execution_options: Optional[Mapping[str, Any]] = None,
+        multiparams: Optional[Sequence[Mapping[str, Any]]] = None,
+        params: Mapping[str, Any] = util.immutabledict(),
+    ) -> Optional[CursorResult]:
+        if isinstance(construct, str):
+            construct = text(construct)
+        if self.as_sql:
+            if multiparams is not None or params:
+                raise TypeError("SQL parameters not allowed with as_sql")
+
+            compile_kw: dict[str, Any]
+            if self.literal_binds and not isinstance(
+                construct, schema.DDLElement
+            ):
+                compile_kw = dict(compile_kwargs={"literal_binds": True})
+            else:
+                compile_kw = {}
+
+            if TYPE_CHECKING:
+                assert isinstance(construct, ClauseElement)
+            compiled = construct.compile(dialect=self.dialect, **compile_kw)
+            self.static_output(
+                str(compiled).replace("\t", "    ").strip()
+                + self.command_terminator
+            )
+            return None
+        else:
+            conn = self.connection
+            assert conn is not None
+            if execution_options:
+                conn = conn.execution_options(**execution_options)
+
+            if params and multiparams is not None:
+                raise TypeError(
+                    "Can't send params and multiparams at the same time"
+                )
+
+            if multiparams:
+                return conn.execute(construct, multiparams)
+            else:
+                return conn.execute(construct, params)
+
+    def execute(
+        self,
+        sql: Union[Executable, str],
+        execution_options: Optional[dict[str, Any]] = None,
+    ) -> None:
+        self._exec(sql, execution_options)
+
+    def alter_column(
+        self,
+        table_name: str,
+        column_name: str,
+        nullable: Optional[bool] = None,
+        server_default: Union[_ServerDefault, Literal[False]] = False,
+        name: Optional[str] = None,
+        type_: Optional[TypeEngine] = None,
+        schema: Optional[str] = None,
+        autoincrement: Optional[bool] = None,
+        comment: Optional[Union[str, Literal[False]]] = False,
+        existing_comment: Optional[str] = None,
+        existing_type: Optional[TypeEngine] = None,
+        existing_server_default: Optional[_ServerDefault] = None,
+        existing_nullable: Optional[bool] = None,
+        existing_autoincrement: Optional[bool] = None,
+        **kw: Any,
+    ) -> None:
+        if autoincrement is not None or existing_autoincrement is not None:
+            util.warn(
+                "autoincrement and existing_autoincrement "
+                "only make sense for MySQL",
+                stacklevel=3,
+            )
+        if nullable is not None:
+            self._exec(
+                base.ColumnNullable(
+                    table_name,
+                    column_name,
+                    nullable,
+                    schema=schema,
+                    existing_type=existing_type,
+                    existing_server_default=existing_server_default,
+                    existing_nullable=existing_nullable,
+                    existing_comment=existing_comment,
+                )
+            )
+        if server_default is not False:
+            kw = {}
+            cls_: Type[
+                Union[
+                    base.ComputedColumnDefault,
+                    base.IdentityColumnDefault,
+                    base.ColumnDefault,
+                ]
+            ]
+            if sqla_compat._server_default_is_computed(
+                server_default, existing_server_default
+            ):
+                cls_ = base.ComputedColumnDefault
+            elif sqla_compat._server_default_is_identity(
+                server_default, existing_server_default
+            ):
+                cls_ = base.IdentityColumnDefault
+                kw["impl"] = self
+            else:
+                cls_ = base.ColumnDefault
+            self._exec(
+                cls_(
+                    table_name,
+                    column_name,
+                    server_default,  # type:ignore[arg-type]
+                    schema=schema,
+                    existing_type=existing_type,
+                    existing_server_default=existing_server_default,
+                    existing_nullable=existing_nullable,
+                    existing_comment=existing_comment,
+                    **kw,
+                )
+            )
+        if type_ is not None:
+            self._exec(
+                base.ColumnType(
+                    table_name,
+                    column_name,
+                    type_,
+                    schema=schema,
+                    existing_type=existing_type,
+                    existing_server_default=existing_server_default,
+                    existing_nullable=existing_nullable,
+                    existing_comment=existing_comment,
+                )
+            )
+
+        if comment is not False:
+            self._exec(
+                base.ColumnComment(
+                    table_name,
+                    column_name,
+                    comment,
+                    schema=schema,
+                    existing_type=existing_type,
+                    existing_server_default=existing_server_default,
+                    existing_nullable=existing_nullable,
+                    existing_comment=existing_comment,
+                )
+            )
+
+        # do the new name last ;)
+        if name is not None:
+            self._exec(
+                base.ColumnName(
+                    table_name,
+                    column_name,
+                    name,
+                    schema=schema,
+                    existing_type=existing_type,
+                    existing_server_default=existing_server_default,
+                    existing_nullable=existing_nullable,
+                )
+            )
+
+    def add_column(
+        self,
+        table_name: str,
+        column: Column[Any],
+        schema: Optional[Union[str, quoted_name]] = None,
+    ) -> None:
+        self._exec(base.AddColumn(table_name, column, schema=schema))
+
+    def drop_column(
+        self,
+        table_name: str,
+        column: Column[Any],
+        schema: Optional[str] = None,
+        **kw,
+    ) -> None:
+        self._exec(base.DropColumn(table_name, column, schema=schema))
+
+    def add_constraint(self, const: Any) -> None:
+        if const._create_rule is None or const._create_rule(self):
+            self._exec(schema.AddConstraint(const))
+
+    def drop_constraint(self, const: Constraint) -> None:
+        self._exec(schema.DropConstraint(const))
+
+    def rename_table(
+        self,
+        old_table_name: str,
+        new_table_name: Union[str, quoted_name],
+        schema: Optional[Union[str, quoted_name]] = None,
+    ) -> None:
+        self._exec(
+            base.RenameTable(old_table_name, new_table_name, schema=schema)
+        )
+
+    def create_table(self, table: Table, **kw: Any) -> None:
+        table.dispatch.before_create(
+            table, self.connection, checkfirst=False, _ddl_runner=self
+        )
+        self._exec(schema.CreateTable(table, **kw))
+        table.dispatch.after_create(
+            table, self.connection, checkfirst=False, _ddl_runner=self
+        )
+        for index in table.indexes:
+            self._exec(schema.CreateIndex(index))
+
+        with_comment = (
+            self.dialect.supports_comments and not self.dialect.inline_comments
+        )
+        comment = table.comment
+        if comment and with_comment:
+            self.create_table_comment(table)
+
+        for column in table.columns:
+            comment = column.comment
+            if comment and with_comment:
+                self.create_column_comment(column)
+
+    def drop_table(self, table: Table, **kw: Any) -> None:
+        table.dispatch.before_drop(
+            table, self.connection, checkfirst=False, _ddl_runner=self
+        )
+        self._exec(schema.DropTable(table, **kw))
+        table.dispatch.after_drop(
+            table, self.connection, checkfirst=False, _ddl_runner=self
+        )
+
+    def create_index(self, index: Index, **kw: Any) -> None:
+        self._exec(schema.CreateIndex(index, **kw))
+
+    def create_table_comment(self, table: Table) -> None:
+        self._exec(schema.SetTableComment(table))
+
+    def drop_table_comment(self, table: Table) -> None:
+        self._exec(schema.DropTableComment(table))
+
+    def create_column_comment(self, column: ColumnElement[Any]) -> None:
+        self._exec(schema.SetColumnComment(column))
+
+    def drop_index(self, index: Index, **kw: Any) -> None:
+        self._exec(schema.DropIndex(index, **kw))
+
+    def bulk_insert(
+        self,
+        table: Union[TableClause, Table],
+        rows: List[dict],
+        multiinsert: bool = True,
+    ) -> None:
+        if not isinstance(rows, list):
+            raise TypeError("List expected")
+        elif rows and not isinstance(rows[0], dict):
+            raise TypeError("List of dictionaries expected")
+        if self.as_sql:
+            for row in rows:
+                self._exec(
+                    table.insert()
+                    .inline()
+                    .values(
+                        **{
+                            k: (
+                                sqla_compat._literal_bindparam(
+                                    k, v, type_=table.c[k].type
+                                )
+                                if not isinstance(
+                                    v, sqla_compat._literal_bindparam
+                                )
+                                else v
+                            )
+                            for k, v in row.items()
+                        }
+                    )
+                )
+        else:
+            if rows:
+                if multiinsert:
+                    self._exec(table.insert().inline(), multiparams=rows)
+                else:
+                    for row in rows:
+                        self._exec(table.insert().inline().values(**row))
+
+    def _tokenize_column_type(self, column: Column) -> Params:
+        definition: str
+        definition = self.dialect.type_compiler.process(column.type).lower()
+
+        # tokenize the SQLAlchemy-generated version of a type, so that
+        # the two can be compared.
+        #
+        # examples:
+        # NUMERIC(10, 5)
+        # TIMESTAMP WITH TIMEZONE
+        # INTEGER UNSIGNED
+        # INTEGER (10) UNSIGNED
+        # INTEGER(10) UNSIGNED
+        # varchar character set utf8
+        #
+
+        tokens: List[str] = re.findall(r"[\w\-_]+|\(.+?\)", definition)
+
+        term_tokens: List[str] = []
+        paren_term = None
+
+        for token in tokens:
+            if re.match(r"^\(.*\)$", token):
+                paren_term = token
+            else:
+                term_tokens.append(token)
+
+        params = Params(term_tokens[0], term_tokens[1:], [], {})
+
+        if paren_term:
+            term: str
+            for term in re.findall("[^(),]+", paren_term):
+                if "=" in term:
+                    key, val = term.split("=")
+                    params.kwargs[key.strip()] = val.strip()
+                else:
+                    params.args.append(term.strip())
+
+        return params
+
+    def _column_types_match(
+        self, inspector_params: Params, metadata_params: Params
+    ) -> bool:
+        if inspector_params.token0 == metadata_params.token0:
+            return True
+
+        synonyms = [{t.lower() for t in batch} for batch in self.type_synonyms]
+        inspector_all_terms = " ".join(
+            [inspector_params.token0] + inspector_params.tokens
+        )
+        metadata_all_terms = " ".join(
+            [metadata_params.token0] + metadata_params.tokens
+        )
+
+        for batch in synonyms:
+            if {inspector_all_terms, metadata_all_terms}.issubset(batch) or {
+                inspector_params.token0,
+                metadata_params.token0,
+            }.issubset(batch):
+                return True
+        return False
+
+    def _column_args_match(
+        self, inspected_params: Params, meta_params: Params
+    ) -> bool:
+        """We want to compare column parameters. However, we only want
+        to compare parameters that are set. If they both have `collation`,
+        we want to make sure they are the same. However, if only one
+        specifies it, dont flag it for being less specific
+        """
+
+        if (
+            len(meta_params.tokens) == len(inspected_params.tokens)
+            and meta_params.tokens != inspected_params.tokens
+        ):
+            return False
+
+        if (
+            len(meta_params.args) == len(inspected_params.args)
+            and meta_params.args != inspected_params.args
+        ):
+            return False
+
+        insp = " ".join(inspected_params.tokens).lower()
+        meta = " ".join(meta_params.tokens).lower()
+
+        for reg in self.type_arg_extract:
+            mi = re.search(reg, insp)
+            mm = re.search(reg, meta)
+
+            if mi and mm and mi.group(1) != mm.group(1):
+                return False
+
+        return True
+
+    def compare_type(
+        self, inspector_column: Column[Any], metadata_column: Column
+    ) -> bool:
+        """Returns True if there ARE differences between the types of the two
+        columns. Takes impl.type_synonyms into account between retrospected
+        and metadata types
+        """
+        inspector_params = self._tokenize_column_type(inspector_column)
+        metadata_params = self._tokenize_column_type(metadata_column)
+
+        if not self._column_types_match(inspector_params, metadata_params):
+            return True
+        if not self._column_args_match(inspector_params, metadata_params):
+            return True
+        return False
+
+    def compare_server_default(
+        self,
+        inspector_column,
+        metadata_column,
+        rendered_metadata_default,
+        rendered_inspector_default,
+    ):
+        return rendered_inspector_default != rendered_metadata_default
+
+    def correct_for_autogen_constraints(
+        self,
+        conn_uniques: Set[UniqueConstraint],
+        conn_indexes: Set[Index],
+        metadata_unique_constraints: Set[UniqueConstraint],
+        metadata_indexes: Set[Index],
+    ) -> None:
+        pass
+
+    def cast_for_batch_migrate(self, existing, existing_transfer, new_type):
+        if existing.type._type_affinity is not new_type._type_affinity:
+            existing_transfer["expr"] = cast(
+                existing_transfer["expr"], new_type
+            )
+
+    def render_ddl_sql_expr(
+        self, expr: ClauseElement, is_server_default: bool = False, **kw: Any
+    ) -> str:
+        """Render a SQL expression that is typically a server default,
+        index expression, etc.
+
+        """
+
+        compile_kw = {"literal_binds": True, "include_table": False}
+
+        return str(
+            expr.compile(dialect=self.dialect, compile_kwargs=compile_kw)
+        )
+
+    def _compat_autogen_column_reflect(self, inspector: Inspector) -> Callable:
+        return self.autogen_column_reflect
+
+    def correct_for_autogen_foreignkeys(
+        self,
+        conn_fks: Set[ForeignKeyConstraint],
+        metadata_fks: Set[ForeignKeyConstraint],
+    ) -> None:
+        pass
+
+    def autogen_column_reflect(self, inspector, table, column_info):
+        """A hook that is attached to the 'column_reflect' event for when
+        a Table is reflected from the database during the autogenerate
+        process.
+
+        Dialects can elect to modify the information gathered here.
+
+        """
+
+    def start_migrations(self) -> None:
+        """A hook called when :meth:`.EnvironmentContext.run_migrations`
+        is called.
+
+        Implementations can set up per-migration-run state here.
+
+        """
+
+    def emit_begin(self) -> None:
+        """Emit the string ``BEGIN``, or the backend-specific
+        equivalent, on the current connection context.
+
+        This is used in offline mode and typically
+        via :meth:`.EnvironmentContext.begin_transaction`.
+
+        """
+        self.static_output("BEGIN" + self.command_terminator)
+
+    def emit_commit(self) -> None:
+        """Emit the string ``COMMIT``, or the backend-specific
+        equivalent, on the current connection context.
+
+        This is used in offline mode and typically
+        via :meth:`.EnvironmentContext.begin_transaction`.
+
+        """
+        self.static_output("COMMIT" + self.command_terminator)
+
+    def render_type(
+        self, type_obj: TypeEngine, autogen_context: AutogenContext
+    ) -> Union[str, Literal[False]]:
+        return False
+
+    def _compare_identity_default(self, metadata_identity, inspector_identity):
+        # ignored contains the attributes that were not considered
+        # because assumed to their default values in the db.
+        diff, ignored = _compare_identity_options(
+            metadata_identity,
+            inspector_identity,
+            schema.Identity(),
+            skip={"always"},
+        )
+
+        meta_always = getattr(metadata_identity, "always", None)
+        inspector_always = getattr(inspector_identity, "always", None)
+        # None and False are the same in this comparison
+        if bool(meta_always) != bool(inspector_always):
+            diff.add("always")
+
+        diff.difference_update(self.identity_attrs_ignore)
+
+        # returns 3 values:
+        return (
+            # different identity attributes
+            diff,
+            # ignored identity attributes
+            ignored,
+            # if the two identity should be considered different
+            bool(diff) or bool(metadata_identity) != bool(inspector_identity),
+        )
+
+    def _compare_index_unique(
+        self, metadata_index: Index, reflected_index: Index
+    ) -> Optional[str]:
+        conn_unique = bool(reflected_index.unique)
+        meta_unique = bool(metadata_index.unique)
+        if conn_unique != meta_unique:
+            return f"unique={conn_unique} to unique={meta_unique}"
+        else:
+            return None
+
+    def _create_metadata_constraint_sig(
+        self, constraint: _autogen._C, **opts: Any
+    ) -> _constraint_sig[_autogen._C]:
+        return _constraint_sig.from_constraint(True, self, constraint, **opts)
+
+    def _create_reflected_constraint_sig(
+        self, constraint: _autogen._C, **opts: Any
+    ) -> _constraint_sig[_autogen._C]:
+        return _constraint_sig.from_constraint(False, self, constraint, **opts)
+
+    def compare_indexes(
+        self,
+        metadata_index: Index,
+        reflected_index: Index,
+    ) -> ComparisonResult:
+        """Compare two indexes by comparing the signature generated by
+        ``create_index_sig``.
+
+        This method returns a ``ComparisonResult``.
+        """
+        msg: List[str] = []
+        unique_msg = self._compare_index_unique(
+            metadata_index, reflected_index
+        )
+        if unique_msg:
+            msg.append(unique_msg)
+        m_sig = self._create_metadata_constraint_sig(metadata_index)
+        r_sig = self._create_reflected_constraint_sig(reflected_index)
+
+        assert _autogen.is_index_sig(m_sig)
+        assert _autogen.is_index_sig(r_sig)
+
+        # The assumption is that the index have no expression
+        for sig in m_sig, r_sig:
+            if sig.has_expressions:
+                log.warning(
+                    "Generating approximate signature for index %s. "
+                    "The dialect "
+                    "implementation should either skip expression indexes "
+                    "or provide a custom implementation.",
+                    sig.const,
+                )
+
+        if m_sig.column_names != r_sig.column_names:
+            msg.append(
+                f"expression {r_sig.column_names} to {m_sig.column_names}"
+            )
+
+        if msg:
+            return ComparisonResult.Different(msg)
+        else:
+            return ComparisonResult.Equal()
+
+    def compare_unique_constraint(
+        self,
+        metadata_constraint: UniqueConstraint,
+        reflected_constraint: UniqueConstraint,
+    ) -> ComparisonResult:
+        """Compare two unique constraints by comparing the two signatures.
+
+        The arguments are two tuples that contain the unique constraint and
+        the signatures generated by ``create_unique_constraint_sig``.
+
+        This method returns a ``ComparisonResult``.
+        """
+        metadata_tup = self._create_metadata_constraint_sig(
+            metadata_constraint
+        )
+        reflected_tup = self._create_reflected_constraint_sig(
+            reflected_constraint
+        )
+
+        meta_sig = metadata_tup.unnamed
+        conn_sig = reflected_tup.unnamed
+        if conn_sig != meta_sig:
+            return ComparisonResult.Different(
+                f"expression {conn_sig} to {meta_sig}"
+            )
+        else:
+            return ComparisonResult.Equal()
+
+    def _skip_functional_indexes(self, metadata_indexes, conn_indexes):
+        conn_indexes_by_name = {c.name: c for c in conn_indexes}
+
+        for idx in list(metadata_indexes):
+            if idx.name in conn_indexes_by_name:
+                continue
+            iex = sqla_compat.is_expression_index(idx)
+            if iex:
+                util.warn(
+                    "autogenerate skipping metadata-specified "
+                    "expression-based index "
+                    f"{idx.name!r}; dialect {self.__dialect__!r} under "
+                    f"SQLAlchemy {sqla_compat.sqlalchemy_version} can't "
+                    "reflect these indexes so they can't be compared"
+                )
+                metadata_indexes.discard(idx)
+
+    def adjust_reflected_dialect_options(
+        self, reflected_object: Dict[str, Any], kind: str
+    ) -> Dict[str, Any]:
+        return reflected_object.get("dialect_options", {})
+
+
+class Params(NamedTuple):
+    token0: str
+    tokens: List[str]
+    args: List[str]
+    kwargs: Dict[str, str]
+
+
+def _compare_identity_options(
+    metadata_io: Union[schema.Identity, schema.Sequence, None],
+    inspector_io: Union[schema.Identity, schema.Sequence, None],
+    default_io: Union[schema.Identity, schema.Sequence],
+    skip: Set[str],
+):
+    # this can be used for identity or sequence compare.
+    # default_io is an instance of IdentityOption with all attributes to the
+    # default value.
+    meta_d = sqla_compat._get_identity_options_dict(metadata_io)
+    insp_d = sqla_compat._get_identity_options_dict(inspector_io)
+
+    diff = set()
+    ignored_attr = set()
+
+    def check_dicts(
+        meta_dict: Mapping[str, Any],
+        insp_dict: Mapping[str, Any],
+        default_dict: Mapping[str, Any],
+        attrs: Iterable[str],
+    ):
+        for attr in set(attrs).difference(skip):
+            meta_value = meta_dict.get(attr)
+            insp_value = insp_dict.get(attr)
+            if insp_value != meta_value:
+                default_value = default_dict.get(attr)
+                if meta_value == default_value:
+                    ignored_attr.add(attr)
+                else:
+                    diff.add(attr)
+
+    check_dicts(
+        meta_d,
+        insp_d,
+        sqla_compat._get_identity_options_dict(default_io),
+        set(meta_d).union(insp_d),
+    )
+    if sqla_compat.identity_has_dialect_kwargs:
+        assert hasattr(default_io, "dialect_kwargs")
+        # use only the dialect kwargs in inspector_io since metadata_io
+        # can have options for many backends
+        check_dicts(
+            getattr(metadata_io, "dialect_kwargs", {}),
+            getattr(inspector_io, "dialect_kwargs", {}),
+            default_io.dialect_kwargs,
+            getattr(inspector_io, "dialect_kwargs", {}),
+        )
+
+    return diff, ignored_attr
diff --git a/.venv/lib/python3.12/site-packages/alembic/ddl/mssql.py b/.venv/lib/python3.12/site-packages/alembic/ddl/mssql.py
new file mode 100644
index 00000000..baa43d5e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/alembic/ddl/mssql.py
@@ -0,0 +1,419 @@
+# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
+# mypy: no-warn-return-any, allow-any-generics
+
+from __future__ import annotations
+
+import re
+from typing import Any
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import TYPE_CHECKING
+from typing import Union
+
+from sqlalchemy import types as sqltypes
+from sqlalchemy.schema import Column
+from sqlalchemy.schema import CreateIndex
+from sqlalchemy.sql.base import Executable
+from sqlalchemy.sql.elements import ClauseElement
+
+from .base import AddColumn
+from .base import alter_column
+from .base import alter_table
+from .base import ColumnDefault
+from .base import ColumnName
+from .base import ColumnNullable
+from .base import ColumnType
+from .base import format_column_name
+from .base import format_server_default
+from .base import format_table_name
+from .base import format_type
+from .base import RenameTable
+from .impl import DefaultImpl
+from .. import util
+from ..util import sqla_compat
+from ..util.sqla_compat import compiles
+
+if TYPE_CHECKING:
+    from typing import Literal
+
+    from sqlalchemy.dialects.mssql.base import MSDDLCompiler
+    from sqlalchemy.dialects.mssql.base import MSSQLCompiler
+    from sqlalchemy.engine.cursor import CursorResult
+    from sqlalchemy.sql.schema import Index
+    from sqlalchemy.sql.schema import Table
+    from sqlalchemy.sql.selectable import TableClause
+    from sqlalchemy.sql.type_api import TypeEngine
+
+    from .base import _ServerDefault
+
+
+class MSSQLImpl(DefaultImpl):
+    __dialect__ = "mssql"
+    transactional_ddl = True
+    batch_separator = "GO"
+
+    type_synonyms = DefaultImpl.type_synonyms + ({"VARCHAR", "NVARCHAR"},)
+    identity_attrs_ignore = DefaultImpl.identity_attrs_ignore + (
+        "minvalue",
+        "maxvalue",
+        "nominvalue",
+        "nomaxvalue",
+        "cycle",
+        "cache",
+    )
+
+    def __init__(self, *arg, **kw) -> None:
+        super().__init__(*arg, **kw)
+        self.batch_separator = self.context_opts.get(
+            "mssql_batch_separator", self.batch_separator
+        )
+
+    def _exec(self, construct: Any, *args, **kw) -> Optional[CursorResult]:
+        result = super()._exec(construct, *args, **kw)
+        if self.as_sql and self.batch_separator:
+            self.static_output(self.batch_separator)
+        return result
+
+    def emit_begin(self) -> None:
+        self.static_output("BEGIN TRANSACTION" + self.command_terminator)
+
+    def emit_commit(self) -> None:
+        super().emit_commit()
+        if self.as_sql and self.batch_separator:
+            self.static_output(self.batch_separator)
+
+    def alter_column(  # type:ignore[override]
+        self,
+        table_name: str,
+        column_name: str,
+        nullable: Optional[bool] = None,
+        server_default: Optional[
+            Union[_ServerDefault, Literal[False]]
+        ] = False,
+        name: Optional[str] = None,
+        type_: Optional[TypeEngine] = None,
+        schema: Optional[str] = None,
+        existing_type: Optional[TypeEngine] = None,
+        existing_server_default: Optional[_ServerDefault] = None,
+        existing_nullable: Optional[bool] = None,
+        **kw: Any,
+    ) -> None:
+        if nullable is not None:
+            if type_ is not None:
+                # the NULL/NOT NULL alter will handle
+                # the type alteration
+                existing_type = type_
+                type_ = None
+            elif existing_type is None:
+                raise util.CommandError(
+                    "MS-SQL ALTER COLUMN operations "
+                    "with NULL or NOT NULL require the "
+                    "existing_type or a new type_ be passed."
+                )
+        elif existing_nullable is not None and type_ is not None:
+            nullable = existing_nullable
+
+            # the NULL/NOT NULL alter will handle
+            # the type alteration
+            existing_type = type_
+            type_ = None
+
+        elif type_ is not None:
+            util.warn(
+                "MS-SQL ALTER COLUMN operations that specify type_= "
+                "should also specify a nullable= or "
+                "existing_nullable= argument to avoid implicit conversion "
+                "of NOT NULL columns to NULL."
+            )
+
+        used_default = False
+        if sqla_compat._server_default_is_identity(
+            server_default, existing_server_default
+        ) or sqla_compat._server_default_is_computed(
+            server_default, existing_server_default
+        ):
+            used_default = True
+            kw["server_default"] = server_default
+            kw["existing_server_default"] = existing_server_default
+
+        super().alter_column(
+            table_name,
+            column_name,
+            nullable=nullable,
+            type_=type_,
+            schema=schema,
+            existing_type=existing_type,
+            existing_nullable=existing_nullable,
+            **kw,
+        )
+
+        if server_default is not False and used_default is False:
+            if existing_server_default is not False or server_default is None:
+                self._exec(
+                    _ExecDropConstraint(
+                        table_name,
+                        column_name,
+                        "sys.default_constraints",
+                        schema,
+                    )
+                )
+            if server_default is not None:
+                super().alter_column(
+                    table_name,
+                    column_name,
+                    schema=schema,
+                    server_default=server_default,
+                )
+
+        if name is not None:
+            super().alter_column(
+                table_name, column_name, schema=schema, name=name
+            )
+
+    def create_index(self, index: Index, **kw: Any) -> None:
+        # this likely defaults to None if not present, so get()
+        # should normally not return the default value.  being
+        # defensive in any case
+        mssql_include = index.kwargs.get("mssql_include", None) or ()
+        assert index.table is not None
+        for col in mssql_include:
+            if col not in index.table.c:
+                index.table.append_column(Column(col, sqltypes.NullType))
+        self._exec(CreateIndex(index, **kw))
+
+    def bulk_insert(  # type:ignore[override]
+        self, table: Union[TableClause, Table], rows: List[dict], **kw: Any
+    ) -> None:
+        if self.as_sql:
+            self._exec(
+                "SET IDENTITY_INSERT %s ON"
+                % self.dialect.identifier_preparer.format_table(table)
+            )
+            super().bulk_insert(table, rows, **kw)
+            self._exec(
+                "SET IDENTITY_INSERT %s OFF"
+                % self.dialect.identifier_preparer.format_table(table)
+            )
+        else:
+            super().bulk_insert(table, rows, **kw)
+
+    def drop_column(
+        self,
+        table_name: str,
+        column: Column[Any],
+        schema: Optional[str] = None,
+        **kw,
+    ) -> None:
+        drop_default = kw.pop("mssql_drop_default", False)
+        if drop_default:
+            self._exec(
+                _ExecDropConstraint(
+                    table_name, column, "sys.default_constraints", schema
+                )
+            )
+        drop_check = kw.pop("mssql_drop_check", False)
+        if drop_check:
+            self._exec(
+                _ExecDropConstraint(
+                    table_name, column, "sys.check_constraints", schema
+                )
+            )
+        drop_fks = kw.pop("mssql_drop_foreign_key", False)
+        if drop_fks:
+            self._exec(_ExecDropFKConstraint(table_name, column, schema))
+        super().drop_column(table_name, column, schema=schema, **kw)
+
+    def compare_server_default(
+        self,
+        inspector_column,
+        metadata_column,
+        rendered_metadata_default,
+        rendered_inspector_default,
+    ):
+        if rendered_metadata_default is not None:
+            rendered_metadata_default = re.sub(
+                r"[\(\) \"\']", "", rendered_metadata_default
+            )
+
+        if rendered_inspector_default is not None:
+            # SQL Server collapses whitespace and adds arbitrary parenthesis
+            # within expressions.   our only option is collapse all of it
+
+            rendered_inspector_default = re.sub(
+                r"[\(\) \"\']", "", rendered_inspector_default
+            )
+
+        return rendered_inspector_default != rendered_metadata_default
+
+    def _compare_identity_default(self, metadata_identity, inspector_identity):
+        diff, ignored, is_alter = super()._compare_identity_default(
+            metadata_identity, inspector_identity
+        )
+
+        if (
+            metadata_identity is None
+            and inspector_identity is not None
+            and not diff
+            and inspector_identity.column is not None
+            and inspector_identity.column.primary_key
+        ):
+            # mssql reflect primary keys with autoincrement as identity
+            # columns. if no different attributes are present ignore them
+            is_alter = False
+
+        return diff, ignored, is_alter
+
+    def adjust_reflected_dialect_options(
+        self, reflected_object: Dict[str, Any], kind: str
+    ) -> Dict[str, Any]:
+        options: Dict[str, Any]
+        options = reflected_object.get("dialect_options", {}).copy()
+        if not options.get("mssql_include"):
+            options.pop("mssql_include", None)
+        if not options.get("mssql_clustered"):
+            options.pop("mssql_clustered", None)
+        return options
+
+
+class _ExecDropConstraint(Executable, ClauseElement):
+    inherit_cache = False
+
+    def __init__(
+        self,
+        tname: str,
+        colname: Union[Column[Any], str],
+        type_: str,
+        schema: Optional[str],
+    ) -> None:
+        self.tname = tname
+        self.colname = colname
+        self.type_ = type_
+        self.schema = schema
+
+
+class _ExecDropFKConstraint(Executable, ClauseElement):
+    inherit_cache = False
+
+    def __init__(
+        self, tname: str, colname: Column[Any], schema: Optional[str]
+    ) -> None:
+        self.tname = tname
+        self.colname = colname
+        self.schema = schema
+
+
+@compiles(_ExecDropConstraint, "mssql")
+def _exec_drop_col_constraint(
+    element: _ExecDropConstraint, compiler: MSSQLCompiler, **kw
+) -> str:
+    schema, tname, colname, type_ = (
+        element.schema,
+        element.tname,
+        element.colname,
+        element.type_,
+    )
+    # from http://www.mssqltips.com/sqlservertip/1425/\
+    # working-with-default-constraints-in-sql-server/
+    return """declare @const_name varchar(256)
+select @const_name = QUOTENAME([name]) from %(type)s
+where parent_object_id = object_id('%(schema_dot)s%(tname)s')
+and col_name(parent_object_id, parent_column_id) = '%(colname)s'
+exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % {
+        "type": type_,
+        "tname": tname,
+        "colname": colname,
+        "tname_quoted": format_table_name(compiler, tname, schema),
+        "schema_dot": schema + "." if schema else "",
+    }
+
+
+@compiles(_ExecDropFKConstraint, "mssql")
+def _exec_drop_col_fk_constraint(
+    element: _ExecDropFKConstraint, compiler: MSSQLCompiler, **kw
+) -> str:
+    schema, tname, colname = element.schema, element.tname, element.colname
+
+    return """declare @const_name varchar(256)
+select @const_name = QUOTENAME([name]) from
+sys.foreign_keys fk join sys.foreign_key_columns fkc
+on fk.object_id=fkc.constraint_object_id
+where fkc.parent_object_id = object_id('%(schema_dot)s%(tname)s')
+and col_name(fkc.parent_object_id, fkc.parent_column_id) = '%(colname)s'
+exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % {
+        "tname": tname,
+        "colname": colname,
+        "tname_quoted": format_table_name(compiler, tname, schema),
+        "schema_dot": schema + "." if schema else "",
+    }
+
+
+@compiles(AddColumn, "mssql")
+def visit_add_column(element: AddColumn, compiler: MSDDLCompiler, **kw) -> str:
+    return "%s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        mssql_add_column(compiler, element.column, **kw),
+    )
+
+
+def mssql_add_column(
+    compiler: MSDDLCompiler, column: Column[Any], **kw
+) -> str:
+    return "ADD %s" % compiler.get_column_specification(column, **kw)
+
+
+@compiles(ColumnNullable, "mssql")
+def visit_column_nullable(
+    element: ColumnNullable, compiler: MSDDLCompiler, **kw
+) -> str:
+    return "%s %s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        format_type(compiler, element.existing_type),  # type: ignore[arg-type]
+        "NULL" if element.nullable else "NOT NULL",
+    )
+
+
+@compiles(ColumnDefault, "mssql")
+def visit_column_default(
+    element: ColumnDefault, compiler: MSDDLCompiler, **kw
+) -> str:
+    # TODO: there can also be a named constraint
+    # with ADD CONSTRAINT here
+    return "%s ADD DEFAULT %s FOR %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_server_default(compiler, element.default),
+        format_column_name(compiler, element.column_name),
+    )
+
+
+@compiles(ColumnName, "mssql")
+def visit_rename_column(
+    element: ColumnName, compiler: MSDDLCompiler, **kw
+) -> str:
+    return "EXEC sp_rename '%s.%s', %s, 'COLUMN'" % (
+        format_table_name(compiler, element.table_name, element.schema),
+        format_column_name(compiler, element.column_name),
+        format_column_name(compiler, element.newname),
+    )
+
+
+@compiles(ColumnType, "mssql")
+def visit_column_type(
+    element: ColumnType, compiler: MSDDLCompiler, **kw
+) -> str:
+    return "%s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        format_type(compiler, element.type_),
+    )
+
+
+@compiles(RenameTable, "mssql")
+def visit_rename_table(
+    element: RenameTable, compiler: MSDDLCompiler, **kw
+) -> str:
+    return "EXEC sp_rename '%s', %s" % (
+        format_table_name(compiler, element.table_name, element.schema),
+        format_table_name(compiler, element.new_table_name, None),
+    )
diff --git a/.venv/lib/python3.12/site-packages/alembic/ddl/mysql.py b/.venv/lib/python3.12/site-packages/alembic/ddl/mysql.py
new file mode 100644
index 00000000..c7b3905c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/alembic/ddl/mysql.py
@@ -0,0 +1,491 @@
+# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
+# mypy: no-warn-return-any, allow-any-generics
+
+from __future__ import annotations
+
+import re
+from typing import Any
+from typing import Optional
+from typing import TYPE_CHECKING
+from typing import Union
+
+from sqlalchemy import schema
+from sqlalchemy import types as sqltypes
+
+from .base import alter_table
+from .base import AlterColumn
+from .base import ColumnDefault
+from .base import ColumnName
+from .base import ColumnNullable
+from .base import ColumnType
+from .base import format_column_name
+from .base import format_server_default
+from .impl import DefaultImpl
+from .. import util
+from ..util import sqla_compat
+from ..util.sqla_compat import _is_type_bound
+from ..util.sqla_compat import compiles
+
+if TYPE_CHECKING:
+    from typing import Literal
+
+    from sqlalchemy.dialects.mysql.base import MySQLDDLCompiler
+    from sqlalchemy.sql.ddl import DropConstraint
+    from sqlalchemy.sql.schema import Constraint
+    from sqlalchemy.sql.type_api import TypeEngine
+
+    from .base import _ServerDefault
+
+
+class MySQLImpl(DefaultImpl):
+    __dialect__ = "mysql"
+
+    transactional_ddl = False
+    type_synonyms = DefaultImpl.type_synonyms + (
+        {"BOOL", "TINYINT"},
+        {"JSON", "LONGTEXT"},
+    )
+    type_arg_extract = [r"character set ([\w\-_]+)", r"collate ([\w\-_]+)"]
+
+    def alter_column(  # type:ignore[override]
+        self,
+        table_name: str,
+        column_name: str,
+        nullable: Optional[bool] = None,
+        server_default: Union[_ServerDefault, Literal[False]] = False,
+        name: Optional[str] = None,
+        type_: Optional[TypeEngine] = None,
+        schema: Optional[str] = None,
+        existing_type: Optional[TypeEngine] = None,
+        existing_server_default: Optional[_ServerDefault] = None,
+        existing_nullable: Optional[bool] = None,
+        autoincrement: Optional[bool] = None,
+        existing_autoincrement: Optional[bool] = None,
+        comment: Optional[Union[str, Literal[False]]] = False,
+        existing_comment: Optional[str] = None,
+        **kw: Any,
+    ) -> None:
+        if sqla_compat._server_default_is_identity(
+            server_default, existing_server_default
+        ) or sqla_compat._server_default_is_computed(
+            server_default, existing_server_default
+        ):
+            # modifying computed or identity columns is not supported
+            # the default will raise
+            super().alter_column(
+                table_name,
+                column_name,
+                nullable=nullable,
+                type_=type_,
+                schema=schema,
+                existing_type=existing_type,
+                existing_nullable=existing_nullable,
+                server_default=server_default,
+                existing_server_default=existing_server_default,
+                **kw,
+            )
+        if name is not None or self._is_mysql_allowed_functional_default(
+            type_ if type_ is not None else existing_type, server_default
+        ):
+            self._exec(
+                MySQLChangeColumn(
+                    table_name,
+                    column_name,
+                    schema=schema,
+                    newname=name if name is not None else column_name,
+                    nullable=(
+                        nullable
+                        if nullable is not None
+                        else (
+                            existing_nullable
+                            if existing_nullable is not None
+                            else True
+                        )
+                    ),
+                    type_=type_ if type_ is not None else existing_type,
+                    default=(
+                        server_default
+                        if server_default is not False
+                        else existing_server_default
+                    ),
+                    autoincrement=(
+                        autoincrement
+                        if autoincrement is not None
+                        else existing_autoincrement
+                    ),
+                    comment=(
+                        comment if comment is not False else existing_comment
+                    ),
+                )
+            )
+        elif (
+            nullable is not None
+            or type_ is not None
+            or autoincrement is not None
+            or comment is not False
+        ):
+            self._exec(
+                MySQLModifyColumn(
+                    table_name,
+                    column_name,
+                    schema=schema,
+                    newname=name if name is not None else column_name,
+                    nullable=(
+                        nullable
+                        if nullable is not None
+                        else (
+                            existing_nullable
+                            if existing_nullable is not None
+                            else True
+                        )
+                    ),
+                    type_=type_ if type_ is not None else existing_type,
+                    default=(
+                        server_default
+                        if server_default is not False
+                        else existing_server_default
+                    ),
+                    autoincrement=(
+                        autoincrement
+                        if autoincrement is not None
+                        else existing_autoincrement
+                    ),
+                    comment=(
+                        comment if comment is not False else existing_comment
+                    ),
+                )
+            )
+        elif server_default is not False:
+            self._exec(
+                MySQLAlterDefault(
+                    table_name, column_name, server_default, schema=schema
+                )
+            )
+
+    def drop_constraint(
+        self,
+        const: Constraint,
+    ) -> None:
+        if isinstance(const, schema.CheckConstraint) and _is_type_bound(const):
+            return
+
+        super().drop_constraint(const)
+
+    def _is_mysql_allowed_functional_default(
+        self,
+        type_: Optional[TypeEngine],
+        server_default: Union[_ServerDefault, Literal[False]],
+    ) -> bool:
+        return (
+            type_ is not None
+            and type_._type_affinity is sqltypes.DateTime
+            and server_default is not None
+        )
+
+    def compare_server_default(
+        self,
+        inspector_column,
+        metadata_column,
+        rendered_metadata_default,
+        rendered_inspector_default,
+    ):
+        # partially a workaround for SQLAlchemy issue #3023; if the
+        # column were created without "NOT NULL", MySQL may have added
+        # an implicit default of '0' which we need to skip
+        # TODO: this is not really covered anymore ?
+        if (
+            metadata_column.type._type_affinity is sqltypes.Integer
+            and inspector_column.primary_key
+            and not inspector_column.autoincrement
+            and not rendered_metadata_default
+            and rendered_inspector_default == "'0'"
+        ):
+            return False
+        elif (
+            rendered_inspector_default
+            and inspector_column.type._type_affinity is sqltypes.Integer
+        ):
+            rendered_inspector_default = (
+                re.sub(r"^'|'$", "", rendered_inspector_default)
+                if rendered_inspector_default is not None
+                else None
+            )
+            return rendered_inspector_default != rendered_metadata_default
+        elif (
+            rendered_metadata_default
+            and metadata_column.type._type_affinity is sqltypes.String
+        ):
+            metadata_default = re.sub(r"^'|'$", "", rendered_metadata_default)
+            return rendered_inspector_default != f"'{metadata_default}'"
+        elif rendered_inspector_default and rendered_metadata_default:
+            # adjust for "function()" vs. "FUNCTION" as can occur particularly
+            # for the CURRENT_TIMESTAMP function on newer MariaDB versions
+
+            # SQLAlchemy MySQL dialect bundles ON UPDATE into the server
+            # default; adjust for this possibly being present.
+            onupdate_ins = re.match(
+                r"(.*) (on update.*?)(?:\(\))?$",
+                rendered_inspector_default.lower(),
+            )
+            onupdate_met = re.match(
+                r"(.*) (on update.*?)(?:\(\))?$",
+                rendered_metadata_default.lower(),
+            )
+
+            if onupdate_ins:
+                if not onupdate_met:
+                    return True
+                elif onupdate_ins.group(2) != onupdate_met.group(2):
+                    return True
+
+                rendered_inspector_default = onupdate_ins.group(1)
+                rendered_metadata_default = onupdate_met.group(1)
+
+            return re.sub(
+                r"(.*?)(?:\(\))?$", r"\1", rendered_inspector_default.lower()
+            ) != re.sub(
+                r"(.*?)(?:\(\))?$", r"\1", rendered_metadata_default.lower()
+            )
+        else:
+            return rendered_inspector_default != rendered_metadata_default
+
+    def correct_for_autogen_constraints(
+        self,
+        conn_unique_constraints,
+        conn_indexes,
+        metadata_unique_constraints,
+        metadata_indexes,
+    ):
+        # TODO: if SQLA 1.0, make use of "duplicates_index"
+        # metadata
+        removed = set()
+        for idx in list(conn_indexes):
+            if idx.unique:
+                continue
+            # MySQL puts implicit indexes on FK columns, even if
+            # composite and even if MyISAM, so can't check this too easily.
+            # the name of the index may be the column name or it may
+            # be the name of the FK constraint.
+            for col in idx.columns:
+                if idx.name == col.name:
+                    conn_indexes.remove(idx)
+                    removed.add(idx.name)
+                    break
+                for fk in col.foreign_keys:
+                    if fk.name == idx.name:
+                        conn_indexes.remove(idx)
+                        removed.add(idx.name)
+                        break
+                if idx.name in removed:
+                    break
+
+        # then remove indexes from the "metadata_indexes"
+        # that we've removed from reflected, otherwise they come out
+        # as adds (see #202)
+        for idx in list(metadata_indexes):
+            if idx.name in removed:
+                metadata_indexes.remove(idx)
+
+    def correct_for_autogen_foreignkeys(self, conn_fks, metadata_fks):
+        conn_fk_by_sig = {
+            self._create_reflected_constraint_sig(fk).unnamed_no_options: fk
+            for fk in conn_fks
+        }
+        metadata_fk_by_sig = {
+            self._create_metadata_constraint_sig(fk).unnamed_no_options: fk
+            for fk in metadata_fks
+        }
+
+        for sig in set(conn_fk_by_sig).intersection(metadata_fk_by_sig):
+            mdfk = metadata_fk_by_sig[sig]
+            cnfk = conn_fk_by_sig[sig]
+            # MySQL considers RESTRICT to be the default and doesn't
+            # report on it.  if the model has explicit RESTRICT and
+            # the conn FK has None, set it to RESTRICT
+            if (
+                mdfk.ondelete is not None
+                and mdfk.ondelete.lower() == "restrict"
+                and cnfk.ondelete is None
+            ):
+                cnfk.ondelete = "RESTRICT"
+            if (
+                mdfk.onupdate is not None
+                and mdfk.onupdate.lower() == "restrict"
+                and cnfk.onupdate is None
+            ):
+                cnfk.onupdate = "RESTRICT"
+
+
+class MariaDBImpl(MySQLImpl):
+    __dialect__ = "mariadb"
+
+
+class MySQLAlterDefault(AlterColumn):
+    def __init__(
+        self,
+        name: str,
+        column_name: str,
+        default: _ServerDefault,
+        schema: Optional[str] = None,
+    ) -> None:
+        super(AlterColumn, self).__init__(name, schema=schema)
+        self.column_name = column_name
+        self.default = default
+
+
+class MySQLChangeColumn(AlterColumn):
+    def __init__(
+        self,
+        name: str,
+        column_name: str,
+        schema: Optional[str] = None,
+        newname: Optional[str] = None,
+        type_: Optional[TypeEngine] = None,
+        nullable: Optional[bool] = None,
+        default: Optional[Union[_ServerDefault, Literal[False]]] = False,
+        autoincrement: Optional[bool] = None,
+        comment: Optional[Union[str, Literal[False]]] = False,
+    ) -> None:
+        super(AlterColumn, self).__init__(name, schema=schema)
+        self.column_name = column_name
+        self.nullable = nullable
+        self.newname = newname
+        self.default = default
+        self.autoincrement = autoincrement
+        self.comment = comment
+        if type_ is None:
+            raise util.CommandError(
+                "All MySQL CHANGE/MODIFY COLUMN operations "
+                "require the existing type."
+            )
+
+        self.type_ = sqltypes.to_instance(type_)
+
+
+class MySQLModifyColumn(MySQLChangeColumn):
+    pass
+
+
+@compiles(ColumnNullable, "mysql", "mariadb")
+@compiles(ColumnName, "mysql", "mariadb")
+@compiles(ColumnDefault, "mysql", "mariadb")
+@compiles(ColumnType, "mysql", "mariadb")
+def _mysql_doesnt_support_individual(element, compiler, **kw):
+    raise NotImplementedError(
+        "Individual alter column constructs not supported by MySQL"
+    )
+
+
+@compiles(MySQLAlterDefault, "mysql", "mariadb")
+def _mysql_alter_default(
+    element: MySQLAlterDefault, compiler: MySQLDDLCompiler, **kw
+) -> str:
+    return "%s ALTER COLUMN %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_column_name(compiler, element.column_name),
+        (
+            "SET DEFAULT %s" % format_server_default(compiler, element.default)
+            if element.default is not None
+            else "DROP DEFAULT"
+        ),
+    )
+
+
+@compiles(MySQLModifyColumn, "mysql", "mariadb")
+def _mysql_modify_column(
+    element: MySQLModifyColumn, compiler: MySQLDDLCompiler, **kw
+) -> str:
+    return "%s MODIFY %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_column_name(compiler, element.column_name),
+        _mysql_colspec(
+            compiler,
+            nullable=element.nullable,
+            server_default=element.default,
+            type_=element.type_,
+            autoincrement=element.autoincrement,
+            comment=element.comment,
+        ),
+    )
+
+
+@compiles(MySQLChangeColumn, "mysql", "mariadb")
+def _mysql_change_column(
+    element: MySQLChangeColumn, compiler: MySQLDDLCompiler, **kw
+) -> str:
+    return "%s CHANGE %s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_column_name(compiler, element.column_name),
+        format_column_name(compiler, element.newname),
+        _mysql_colspec(
+            compiler,
+            nullable=element.nullable,
+            server_default=element.default,
+            type_=element.type_,
+            autoincrement=element.autoincrement,
+            comment=element.comment,
+        ),
+    )
+
+
+def _mysql_colspec(
+    compiler: MySQLDDLCompiler,
+    nullable: Optional[bool],
+    server_default: Optional[Union[_ServerDefault, Literal[False]]],
+    type_: TypeEngine,
+    autoincrement: Optional[bool],
+    comment: Optional[Union[str, Literal[False]]],
+) -> str:
+    spec = "%s %s" % (
+        compiler.dialect.type_compiler.process(type_),
+        "NULL" if nullable else "NOT NULL",
+    )
+    if autoincrement:
+        spec += " AUTO_INCREMENT"
+    if server_default is not False and server_default is not None:
+        spec += " DEFAULT %s" % format_server_default(compiler, server_default)
+    if comment:
+        spec += " COMMENT %s" % compiler.sql_compiler.render_literal_value(
+            comment, sqltypes.String()
+        )
+
+    return spec
+
+
+@compiles(schema.DropConstraint, "mysql", "mariadb")
+def _mysql_drop_constraint(
+    element: DropConstraint, compiler: MySQLDDLCompiler, **kw
+) -> str:
+    """Redefine SQLAlchemy's drop constraint to
+    raise errors for invalid constraint type."""
+
+    constraint = element.element
+    if isinstance(
+        constraint,
+        (
+            schema.ForeignKeyConstraint,
+            schema.PrimaryKeyConstraint,
+            schema.UniqueConstraint,
+        ),
+    ):
+        assert not kw
+        return compiler.visit_drop_constraint(element)
+    elif isinstance(constraint, schema.CheckConstraint):
+        # note that SQLAlchemy as of 1.2 does not yet support
+        # DROP CONSTRAINT for MySQL/MariaDB, so we implement fully
+        # here.
+        if compiler.dialect.is_mariadb:  # type: ignore[attr-defined]
+            return "ALTER TABLE %s DROP CONSTRAINT %s" % (
+                compiler.preparer.format_table(constraint.table),
+                compiler.preparer.format_constraint(constraint),
+            )
+        else:
+            return "ALTER TABLE %s DROP CHECK %s" % (
+                compiler.preparer.format_table(constraint.table),
+                compiler.preparer.format_constraint(constraint),
+            )
+    else:
+        raise NotImplementedError(
+            "No generic 'DROP CONSTRAINT' in MySQL - "
+            "please specify constraint type"
+        )
diff --git a/.venv/lib/python3.12/site-packages/alembic/ddl/oracle.py b/.venv/lib/python3.12/site-packages/alembic/ddl/oracle.py
new file mode 100644
index 00000000..eac99124
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/alembic/ddl/oracle.py
@@ -0,0 +1,202 @@
+# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
+# mypy: no-warn-return-any, allow-any-generics
+
+from __future__ import annotations
+
+import re
+from typing import Any
+from typing import Optional
+from typing import TYPE_CHECKING
+
+from sqlalchemy.sql import sqltypes
+
+from .base import AddColumn
+from .base import alter_table
+from .base import ColumnComment
+from .base import ColumnDefault
+from .base import ColumnName
+from .base import ColumnNullable
+from .base import ColumnType
+from .base import format_column_name
+from .base import format_server_default
+from .base import format_table_name
+from .base import format_type
+from .base import IdentityColumnDefault
+from .base import RenameTable
+from .impl import DefaultImpl
+from ..util.sqla_compat import compiles
+
+if TYPE_CHECKING:
+    from sqlalchemy.dialects.oracle.base import OracleDDLCompiler
+    from sqlalchemy.engine.cursor import CursorResult
+    from sqlalchemy.sql.schema import Column
+
+
+class OracleImpl(DefaultImpl):
+    __dialect__ = "oracle"
+    transactional_ddl = False
+    batch_separator = "/"
+    command_terminator = ""
+    type_synonyms = DefaultImpl.type_synonyms + (
+        {"VARCHAR", "VARCHAR2"},
+        {"BIGINT", "INTEGER", "SMALLINT", "DECIMAL", "NUMERIC", "NUMBER"},
+        {"DOUBLE", "FLOAT", "DOUBLE_PRECISION"},
+    )
+    identity_attrs_ignore = ()
+
+    def __init__(self, *arg, **kw) -> None:
+        super().__init__(*arg, **kw)
+        self.batch_separator = self.context_opts.get(
+            "oracle_batch_separator", self.batch_separator
+        )
+
+    def _exec(self, construct: Any, *args, **kw) -> Optional[CursorResult]:
+        result = super()._exec(construct, *args, **kw)
+        if self.as_sql and self.batch_separator:
+            self.static_output(self.batch_separator)
+        return result
+
+    def compare_server_default(
+        self,
+        inspector_column,
+        metadata_column,
+        rendered_metadata_default,
+        rendered_inspector_default,
+    ):
+        if rendered_metadata_default is not None:
+            rendered_metadata_default = re.sub(
+                r"^\((.+)\)$", r"\1", rendered_metadata_default
+            )
+
+            rendered_metadata_default = re.sub(
+                r"^\"?'(.+)'\"?$", r"\1", rendered_metadata_default
+            )
+
+        if rendered_inspector_default is not None:
+            rendered_inspector_default = re.sub(
+                r"^\((.+)\)$", r"\1", rendered_inspector_default
+            )
+
+            rendered_inspector_default = re.sub(
+                r"^\"?'(.+)'\"?$", r"\1", rendered_inspector_default
+            )
+
+            rendered_inspector_default = rendered_inspector_default.strip()
+        return rendered_inspector_default != rendered_metadata_default
+
+    def emit_begin(self) -> None:
+        self._exec("SET TRANSACTION READ WRITE")
+
+    def emit_commit(self) -> None:
+        self._exec("COMMIT")
+
+
+@compiles(AddColumn, "oracle")
+def visit_add_column(
+    element: AddColumn, compiler: OracleDDLCompiler, **kw
+) -> str:
+    return "%s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        add_column(compiler, element.column, **kw),
+    )
+
+
+@compiles(ColumnNullable, "oracle")
+def visit_column_nullable(
+    element: ColumnNullable, compiler: OracleDDLCompiler, **kw
+) -> str:
+    return "%s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        "NULL" if element.nullable else "NOT NULL",
+    )
+
+
+@compiles(ColumnType, "oracle")
+def visit_column_type(
+    element: ColumnType, compiler: OracleDDLCompiler, **kw
+) -> str:
+    return "%s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        "%s" % format_type(compiler, element.type_),
+    )
+
+
+@compiles(ColumnName, "oracle")
+def visit_column_name(
+    element: ColumnName, compiler: OracleDDLCompiler, **kw
+) -> str:
+    return "%s RENAME COLUMN %s TO %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_column_name(compiler, element.column_name),
+        format_column_name(compiler, element.newname),
+    )
+
+
+@compiles(ColumnDefault, "oracle")
+def visit_column_default(
+    element: ColumnDefault, compiler: OracleDDLCompiler, **kw
+) -> str:
+    return "%s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        (
+            "DEFAULT %s" % format_server_default(compiler, element.default)
+            if element.default is not None
+            else "DEFAULT NULL"
+        ),
+    )
+
+
+@compiles(ColumnComment, "oracle")
+def visit_column_comment(
+    element: ColumnComment, compiler: OracleDDLCompiler, **kw
+) -> str:
+    ddl = "COMMENT ON COLUMN {table_name}.{column_name} IS {comment}"
+
+    comment = compiler.sql_compiler.render_literal_value(
+        (element.comment if element.comment is not None else ""),
+        sqltypes.String(),
+    )
+
+    return ddl.format(
+        table_name=element.table_name,
+        column_name=element.column_name,
+        comment=comment,
+    )
+
+
+@compiles(RenameTable, "oracle")
+def visit_rename_table(
+    element: RenameTable, compiler: OracleDDLCompiler, **kw
+) -> str:
+    return "%s RENAME TO %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_table_name(compiler, element.new_table_name, None),
+    )
+
+
+def alter_column(compiler: OracleDDLCompiler, name: str) -> str:
+    return "MODIFY %s" % format_column_name(compiler, name)
+
+
+def add_column(compiler: OracleDDLCompiler, column: Column[Any], **kw) -> str:
+    return "ADD %s" % compiler.get_column_specification(column, **kw)
+
+
+@compiles(IdentityColumnDefault, "oracle")
+def visit_identity_column(
+    element: IdentityColumnDefault, compiler: OracleDDLCompiler, **kw
+):
+    text = "%s %s " % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+    )
+    if element.default is None:
+        # drop identity
+        text += "DROP IDENTITY"
+        return text
+    else:
+        text += compiler.visit_identity_column(element.default)
+        return text
diff --git a/.venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py b/.venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py
new file mode 100644
index 00000000..7cd8d35b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py
@@ -0,0 +1,850 @@
+# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
+# mypy: no-warn-return-any, allow-any-generics
+
+from __future__ import annotations
+
+import logging
+import re
+from typing import Any
+from typing import cast
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Sequence
+from typing import Tuple
+from typing import TYPE_CHECKING
+from typing import Union
+
+from sqlalchemy import Column
+from sqlalchemy import Float
+from sqlalchemy import Identity
+from sqlalchemy import literal_column
+from sqlalchemy import Numeric
+from sqlalchemy import select
+from sqlalchemy import text
+from sqlalchemy import types as sqltypes
+from sqlalchemy.dialects.postgresql import BIGINT
+from sqlalchemy.dialects.postgresql import ExcludeConstraint
+from sqlalchemy.dialects.postgresql import INTEGER
+from sqlalchemy.schema import CreateIndex
+from sqlalchemy.sql.elements import ColumnClause
+from sqlalchemy.sql.elements import TextClause
+from sqlalchemy.sql.functions import FunctionElement
+from sqlalchemy.types import NULLTYPE
+
+from .base import alter_column
+from .base import alter_table
+from .base import AlterColumn
+from .base import ColumnComment
+from .base import format_column_name
+from .base import format_table_name
+from .base import format_type
+from .base import IdentityColumnDefault
+from .base import RenameTable
+from .impl import ComparisonResult
+from .impl import DefaultImpl
+from .. import util
+from ..autogenerate import render
+from ..operations import ops
+from ..operations import schemaobj
+from ..operations.base import BatchOperations
+from ..operations.base import Operations
+from ..util import sqla_compat
+from ..util.sqla_compat import compiles
+
+if TYPE_CHECKING:
+    from typing import Literal
+
+    from sqlalchemy import Index
+    from sqlalchemy import UniqueConstraint
+    from sqlalchemy.dialects.postgresql.array import ARRAY
+    from sqlalchemy.dialects.postgresql.base import PGDDLCompiler
+    from sqlalchemy.dialects.postgresql.hstore import HSTORE
+    from sqlalchemy.dialects.postgresql.json import JSON
+    from sqlalchemy.dialects.postgresql.json import JSONB
+    from sqlalchemy.sql.elements import ClauseElement
+    from sqlalchemy.sql.elements import ColumnElement
+    from sqlalchemy.sql.elements import quoted_name
+    from sqlalchemy.sql.schema import MetaData
+    from sqlalchemy.sql.schema import Table
+    from sqlalchemy.sql.type_api import TypeEngine
+
+    from .base import _ServerDefault
+    from ..autogenerate.api import AutogenContext
+    from ..autogenerate.render import _f_name
+    from ..runtime.migration import MigrationContext
+
+
+log = logging.getLogger(__name__)
+
+
+class PostgresqlImpl(DefaultImpl):
+    __dialect__ = "postgresql"
+    transactional_ddl = True
+    type_synonyms = DefaultImpl.type_synonyms + (
+        {"FLOAT", "DOUBLE PRECISION"},
+    )
+
+    def create_index(self, index: Index, **kw: Any) -> None:
+        # this likely defaults to None if not present, so get()
+        # should normally not return the default value.  being
+        # defensive in any case
+        postgresql_include = index.kwargs.get("postgresql_include", None) or ()
+        for col in postgresql_include:
+            if col not in index.table.c:  # type: ignore[union-attr]
+                index.table.append_column(  # type: ignore[union-attr]
+                    Column(col, sqltypes.NullType)
+                )
+        self._exec(CreateIndex(index, **kw))
+
+    def prep_table_for_batch(self, batch_impl, table):
+        for constraint in table.constraints:
+            if (
+                constraint.name is not None
+                and constraint.name in batch_impl.named_constraints
+            ):
+                self.drop_constraint(constraint)
+
+    def compare_server_default(
+        self,
+        inspector_column,
+        metadata_column,
+        rendered_metadata_default,
+        rendered_inspector_default,
+    ):
+        # don't do defaults for SERIAL columns
+        if (
+            metadata_column.primary_key
+            and metadata_column is metadata_column.table._autoincrement_column
+        ):
+            return False
+
+        conn_col_default = rendered_inspector_default
+
+        defaults_equal = conn_col_default == rendered_metadata_default
+        if defaults_equal:
+            return False
+
+        if None in (
+            conn_col_default,
+            rendered_metadata_default,
+            metadata_column.server_default,
+        ):
+            return not defaults_equal
+
+        metadata_default = metadata_column.server_default.arg
+
+        if isinstance(metadata_default, str):
+            if not isinstance(inspector_column.type, (Numeric, Float)):
+                metadata_default = re.sub(r"^'|'$", "", metadata_default)
+                metadata_default = f"'{metadata_default}'"
+
+            metadata_default = literal_column(metadata_default)
+
+        # run a real compare against the server
+        conn = self.connection
+        assert conn is not None
+        return not conn.scalar(
+            select(literal_column(conn_col_default) == metadata_default)
+        )
+
+    def alter_column(  # type:ignore[override]
+        self,
+        table_name: str,
+        column_name: str,
+        nullable: Optional[bool] = None,
+        server_default: Union[_ServerDefault, Literal[False]] = False,
+        name: Optional[str] = None,
+        type_: Optional[TypeEngine] = None,
+        schema: Optional[str] = None,
+        autoincrement: Optional[bool] = None,
+        existing_type: Optional[TypeEngine] = None,
+        existing_server_default: Optional[_ServerDefault] = None,
+        existing_nullable: Optional[bool] = None,
+        existing_autoincrement: Optional[bool] = None,
+        **kw: Any,
+    ) -> None:
+        using = kw.pop("postgresql_using", None)
+
+        if using is not None and type_ is None:
+            raise util.CommandError(
+                "postgresql_using must be used with the type_ parameter"
+            )
+
+        if type_ is not None:
+            self._exec(
+                PostgresqlColumnType(
+                    table_name,
+                    column_name,
+                    type_,
+                    schema=schema,
+                    using=using,
+                    existing_type=existing_type,
+                    existing_server_default=existing_server_default,
+                    existing_nullable=existing_nullable,
+                )
+            )
+
+        super().alter_column(
+            table_name,
+            column_name,
+            nullable=nullable,
+            server_default=server_default,
+            name=name,
+            schema=schema,
+            autoincrement=autoincrement,
+            existing_type=existing_type,
+            existing_server_default=existing_server_default,
+            existing_nullable=existing_nullable,
+            existing_autoincrement=existing_autoincrement,
+            **kw,
+        )
+
+    def autogen_column_reflect(self, inspector, table, column_info):
+        if column_info.get("default") and isinstance(
+            column_info["type"], (INTEGER, BIGINT)
+        ):
+            seq_match = re.match(
+                r"nextval\('(.+?)'::regclass\)", column_info["default"]
+            )
+            if seq_match:
+                info = sqla_compat._exec_on_inspector(
+                    inspector,
+                    text(
+                        "select c.relname, a.attname "
+                        "from pg_class as c join "
+                        "pg_depend d on d.objid=c.oid and "
+                        "d.classid='pg_class'::regclass and "
+                        "d.refclassid='pg_class'::regclass "
+                        "join pg_class t on t.oid=d.refobjid "
+                        "join pg_attribute a on a.attrelid=t.oid and "
+                        "a.attnum=d.refobjsubid "
+                        "where c.relkind='S' and "
+                        "c.oid=cast(:seqname as regclass)"
+                    ),
+                    seqname=seq_match.group(1),
+                ).first()
+                if info:
+                    seqname, colname = info
+                    if colname == column_info["name"]:
+                        log.info(
+                            "Detected sequence named '%s' as "
+                            "owned by integer column '%s(%s)', "
+                            "assuming SERIAL and omitting",
+                            seqname,
+                            table.name,
+                            colname,
+                        )
+                        # sequence, and the owner is this column,
+                        # its a SERIAL - whack it!
+                        del column_info["default"]
+
+    def correct_for_autogen_constraints(
+        self,
+        conn_unique_constraints,
+        conn_indexes,
+        metadata_unique_constraints,
+        metadata_indexes,
+    ):
+        doubled_constraints = {
+            index
+            for index in conn_indexes
+            if index.info.get("duplicates_constraint")
+        }
+
+        for ix in doubled_constraints:
+            conn_indexes.remove(ix)
+
+        if not sqla_compat.sqla_2:
+            self._skip_functional_indexes(metadata_indexes, conn_indexes)
+
+    # pg behavior regarding modifiers
+    # | # | compiled sql     | returned sql     | regexp. group is removed |
+    # | - | ---------------- | -----------------| ------------------------ |
+    # | 1 | nulls first      | nulls first      | -                        |
+    # | 2 | nulls last       |                  | (?<! desc)( nulls last)$ |
+    # | 3 | asc              |                  | ( asc)$                  |
+    # | 4 | asc nulls first  | nulls first      | ( asc) nulls first$      |
+    # | 5 | asc nulls last   |                  | ( asc nulls last)$       |
+    # | 6 | desc             | desc             | -                        |
+    # | 7 | desc nulls first | desc             | desc( nulls first)$      |
+    # | 8 | desc nulls last  | desc nulls last  | -                        |
+    _default_modifiers_re = (  # order of case 2 and 5 matters
+        re.compile("( asc nulls last)$"),  # case 5
+        re.compile("(?<! desc)( nulls last)$"),  # case 2
+        re.compile("( asc)$"),  # case 3
+        re.compile("( asc) nulls first$"),  # case 4
+        re.compile(" desc( nulls first)$"),  # case 7
+    )
+
+    def _cleanup_index_expr(self, index: Index, expr: str) -> str:
+        expr = expr.lower().replace('"', "").replace("'", "")
+        if index.table is not None:
+            # should not be needed, since include_table=False is in compile
+            expr = expr.replace(f"{index.table.name.lower()}.", "")
+
+        if "::" in expr:
+            # strip :: cast. types can have spaces in them
+            expr = re.sub(r"(::[\w ]+\w)", "", expr)
+
+        while expr and expr[0] == "(" and expr[-1] == ")":
+            expr = expr[1:-1]
+
+        # NOTE: when parsing the connection expression this cleanup could
+        # be skipped
+        for rs in self._default_modifiers_re:
+            if match := rs.search(expr):
+                start, end = match.span(1)
+                expr = expr[:start] + expr[end:]
+                break
+
+        while expr and expr[0] == "(" and expr[-1] == ")":
+            expr = expr[1:-1]
+
+        # strip casts
+        cast_re = re.compile(r"cast\s*\(")
+        if cast_re.match(expr):
+            expr = cast_re.sub("", expr)
+            # remove the as type
+            expr = re.sub(r"as\s+[^)]+\)", "", expr)
+        # remove spaces
+        expr = expr.replace(" ", "")
+        return expr
+
+    def _dialect_options(
+        self, item: Union[Index, UniqueConstraint]
+    ) -> Tuple[Any, ...]:
+        # only the positive case is returned by sqlalchemy reflection so
+        # None and False are threated the same
+        if item.dialect_kwargs.get("postgresql_nulls_not_distinct"):
+            return ("nulls_not_distinct",)
+        return ()
+
+    def compare_indexes(
+        self,
+        metadata_index: Index,
+        reflected_index: Index,
+    ) -> ComparisonResult:
+        msg = []
+        unique_msg = self._compare_index_unique(
+            metadata_index, reflected_index
+        )
+        if unique_msg:
+            msg.append(unique_msg)
+        m_exprs = metadata_index.expressions
+        r_exprs = reflected_index.expressions
+        if len(m_exprs) != len(r_exprs):
+            msg.append(f"expression number {len(r_exprs)} to {len(m_exprs)}")
+        if msg:
+            # no point going further, return early
+            return ComparisonResult.Different(msg)
+        skip = []
+        for pos, (m_e, r_e) in enumerate(zip(m_exprs, r_exprs), 1):
+            m_compile = self._compile_element(m_e)
+            m_text = self._cleanup_index_expr(metadata_index, m_compile)
+            # print(f"META ORIG: {m_compile!r} CLEANUP: {m_text!r}")
+            r_compile = self._compile_element(r_e)
+            r_text = self._cleanup_index_expr(metadata_index, r_compile)
+            # print(f"CONN ORIG: {r_compile!r} CLEANUP: {r_text!r}")
+            if m_text == r_text:
+                continue  # expressions these are equal
+            elif m_compile.strip().endswith("_ops") and (
+                " " in m_compile or ")" in m_compile  # is an expression
+            ):
+                skip.append(
+                    f"expression #{pos} {m_compile!r} detected "
+                    "as including operator clause."
+                )
+                util.warn(
+                    f"Expression #{pos} {m_compile!r} in index "
+                    f"{reflected_index.name!r} detected to include "
+                    "an operator clause. Expression compare cannot proceed. "
+                    "Please move the operator clause to the "
+                    "``postgresql_ops`` dict to enable proper compare "
+                    "of the index expressions: "
+                    "https://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#operator-classes",  # noqa: E501
+                )
+            else:
+                msg.append(f"expression #{pos} {r_compile!r} to {m_compile!r}")
+
+        m_options = self._dialect_options(metadata_index)
+        r_options = self._dialect_options(reflected_index)
+        if m_options != r_options:
+            msg.extend(f"options {r_options} to {m_options}")
+
+        if msg:
+            return ComparisonResult.Different(msg)
+        elif skip:
+            # if there are other changes detected don't skip the index
+            return ComparisonResult.Skip(skip)
+        else:
+            return ComparisonResult.Equal()
+
+    def compare_unique_constraint(
+        self,
+        metadata_constraint: UniqueConstraint,
+        reflected_constraint: UniqueConstraint,
+    ) -> ComparisonResult:
+        metadata_tup = self._create_metadata_constraint_sig(
+            metadata_constraint
+        )
+        reflected_tup = self._create_reflected_constraint_sig(
+            reflected_constraint
+        )
+
+        meta_sig = metadata_tup.unnamed
+        conn_sig = reflected_tup.unnamed
+        if conn_sig != meta_sig:
+            return ComparisonResult.Different(
+                f"expression {conn_sig} to {meta_sig}"
+            )
+
+        metadata_do = self._dialect_options(metadata_tup.const)
+        conn_do = self._dialect_options(reflected_tup.const)
+        if metadata_do != conn_do:
+            return ComparisonResult.Different(
+                f"expression {conn_do} to {metadata_do}"
+            )
+
+        return ComparisonResult.Equal()
+
+    def adjust_reflected_dialect_options(
+        self, reflected_options: Dict[str, Any], kind: str
+    ) -> Dict[str, Any]:
+        options: Dict[str, Any]
+        options = reflected_options.get("dialect_options", {}).copy()
+        if not options.get("postgresql_include"):
+            options.pop("postgresql_include", None)
+        return options
+
+    def _compile_element(self, element: Union[ClauseElement, str]) -> str:
+        if isinstance(element, str):
+            return element
+        return element.compile(
+            dialect=self.dialect,
+            compile_kwargs={"literal_binds": True, "include_table": False},
+        ).string
+
+    def render_ddl_sql_expr(
+        self,
+        expr: ClauseElement,
+        is_server_default: bool = False,
+        is_index: bool = False,
+        **kw: Any,
+    ) -> str:
+        """Render a SQL expression that is typically a server default,
+        index expression, etc.
+
+        """
+
+        # apply self_group to index expressions;
+        # see https://github.com/sqlalchemy/sqlalchemy/blob/
+        # 82fa95cfce070fab401d020c6e6e4a6a96cc2578/
+        # lib/sqlalchemy/dialects/postgresql/base.py#L2261
+        if is_index and not isinstance(expr, ColumnClause):
+            expr = expr.self_group()
+
+        return super().render_ddl_sql_expr(
+            expr, is_server_default=is_server_default, is_index=is_index, **kw
+        )
+
+    def render_type(
+        self, type_: TypeEngine, autogen_context: AutogenContext
+    ) -> Union[str, Literal[False]]:
+        mod = type(type_).__module__
+        if not mod.startswith("sqlalchemy.dialects.postgresql"):
+            return False
+
+        if hasattr(self, "_render_%s_type" % type_.__visit_name__):
+            meth = getattr(self, "_render_%s_type" % type_.__visit_name__)
+            return meth(type_, autogen_context)
+
+        return False
+
+    def _render_HSTORE_type(
+        self, type_: HSTORE, autogen_context: AutogenContext
+    ) -> str:
+        return cast(
+            str,
+            render._render_type_w_subtype(
+                type_, autogen_context, "text_type", r"(.+?\(.*text_type=)"
+            ),
+        )
+
+    def _render_ARRAY_type(
+        self, type_: ARRAY, autogen_context: AutogenContext
+    ) -> str:
+        return cast(
+            str,
+            render._render_type_w_subtype(
+                type_, autogen_context, "item_type", r"(.+?\()"
+            ),
+        )
+
+    def _render_JSON_type(
+        self, type_: JSON, autogen_context: AutogenContext
+    ) -> str:
+        return cast(
+            str,
+            render._render_type_w_subtype(
+                type_, autogen_context, "astext_type", r"(.+?\(.*astext_type=)"
+            ),
+        )
+
+    def _render_JSONB_type(
+        self, type_: JSONB, autogen_context: AutogenContext
+    ) -> str:
+        return cast(
+            str,
+            render._render_type_w_subtype(
+                type_, autogen_context, "astext_type", r"(.+?\(.*astext_type=)"
+            ),
+        )
+
+
+class PostgresqlColumnType(AlterColumn):
+    def __init__(
+        self, name: str, column_name: str, type_: TypeEngine, **kw
+    ) -> None:
+        using = kw.pop("using", None)
+        super().__init__(name, column_name, **kw)
+        self.type_ = sqltypes.to_instance(type_)
+        self.using = using
+
+
+@compiles(RenameTable, "postgresql")
+def visit_rename_table(
+    element: RenameTable, compiler: PGDDLCompiler, **kw
+) -> str:
+    return "%s RENAME TO %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_table_name(compiler, element.new_table_name, None),
+    )
+
+
+@compiles(PostgresqlColumnType, "postgresql")
+def visit_column_type(
+    element: PostgresqlColumnType, compiler: PGDDLCompiler, **kw
+) -> str:
+    return "%s %s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        "TYPE %s" % format_type(compiler, element.type_),
+        "USING %s" % element.using if element.using else "",
+    )
+
+
+@compiles(ColumnComment, "postgresql")
+def visit_column_comment(
+    element: ColumnComment, compiler: PGDDLCompiler, **kw
+) -> str:
+    ddl = "COMMENT ON COLUMN {table_name}.{column_name} IS {comment}"
+    comment = (
+        compiler.sql_compiler.render_literal_value(
+            element.comment, sqltypes.String()
+        )
+        if element.comment is not None
+        else "NULL"
+    )
+
+    return ddl.format(
+        table_name=format_table_name(
+            compiler, element.table_name, element.schema
+        ),
+        column_name=format_column_name(compiler, element.column_name),
+        comment=comment,
+    )
+
+
+@compiles(IdentityColumnDefault, "postgresql")
+def visit_identity_column(
+    element: IdentityColumnDefault, compiler: PGDDLCompiler, **kw
+):
+    text = "%s %s " % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+    )
+    if element.default is None:
+        # drop identity
+        text += "DROP IDENTITY"
+        return text
+    elif element.existing_server_default is None:
+        # add identity options
+        text += "ADD "
+        text += compiler.visit_identity_column(element.default)
+        return text
+    else:
+        # alter identity
+        diff, _, _ = element.impl._compare_identity_default(
+            element.default, element.existing_server_default
+        )
+        identity = element.default
+        for attr in sorted(diff):
+            if attr == "always":
+                text += "SET GENERATED %s " % (
+                    "ALWAYS" if identity.always else "BY DEFAULT"
+                )
+            else:
+                text += "SET %s " % compiler.get_identity_options(
+                    Identity(**{attr: getattr(identity, attr)})
+                )
+        return text
+
+
+@Operations.register_operation("create_exclude_constraint")
+@BatchOperations.register_operation(
+    "create_exclude_constraint", "batch_create_exclude_constraint"
+)
+@ops.AddConstraintOp.register_add_constraint("exclude_constraint")
+class CreateExcludeConstraintOp(ops.AddConstraintOp):
+    """Represent a create exclude constraint operation."""
+
+    constraint_type = "exclude"
+
+    def __init__(
+        self,
+        constraint_name: sqla_compat._ConstraintName,
+        table_name: Union[str, quoted_name],
+        elements: Union[
+            Sequence[Tuple[str, str]],
+            Sequence[Tuple[ColumnClause[Any], str]],
+        ],
+        where: Optional[Union[ColumnElement[bool], str]] = None,
+        schema: Optional[str] = None,
+        _orig_constraint: Optional[ExcludeConstraint] = None,
+        **kw,
+    ) -> None:
+        self.constraint_name = constraint_name
+        self.table_name = table_name
+        self.elements = elements
+        self.where = where
+        self.schema = schema
+        self._orig_constraint = _orig_constraint
+        self.kw = kw
+
+    @classmethod
+    def from_constraint(  # type:ignore[override]
+        cls, constraint: ExcludeConstraint
+    ) -> CreateExcludeConstraintOp:
+        constraint_table = sqla_compat._table_for_constraint(constraint)
+        return cls(
+            constraint.name,
+            constraint_table.name,
+            [  # type: ignore
+                (expr, op) for expr, name, op in constraint._render_exprs
+            ],
+            where=cast("ColumnElement[bool] | None", constraint.where),
+            schema=constraint_table.schema,
+            _orig_constraint=constraint,
+            deferrable=constraint.deferrable,
+            initially=constraint.initially,
+            using=constraint.using,
+        )
+
+    def to_constraint(
+        self, migration_context: Optional[MigrationContext] = None
+    ) -> ExcludeConstraint:
+        if self._orig_constraint is not None:
+            return self._orig_constraint
+        schema_obj = schemaobj.SchemaObjects(migration_context)
+        t = schema_obj.table(self.table_name, schema=self.schema)
+        excl = ExcludeConstraint(
+            *self.elements,
+            name=self.constraint_name,
+            where=self.where,
+            **self.kw,
+        )
+        for (
+            expr,
+            name,
+            oper,
+        ) in excl._render_exprs:
+            t.append_column(Column(name, NULLTYPE))
+        t.append_constraint(excl)
+        return excl
+
+    @classmethod
+    def create_exclude_constraint(
+        cls,
+        operations: Operations,
+        constraint_name: str,
+        table_name: str,
+        *elements: Any,
+        **kw: Any,
+    ) -> Optional[Table]:
+        """Issue an alter to create an EXCLUDE constraint using the
+        current migration context.
+
+        .. note::  This method is Postgresql specific, and additionally
+           requires at least SQLAlchemy 1.0.
+
+        e.g.::
+
+            from alembic import op
+
+            op.create_exclude_constraint(
+                "user_excl",
+                "user",
+                ("period", "&&"),
+                ("group", "="),
+                where=("group != 'some group'"),
+            )
+
+        Note that the expressions work the same way as that of
+        the ``ExcludeConstraint`` object itself; if plain strings are
+        passed, quoting rules must be applied manually.
+
+        :param name: Name of the constraint.
+        :param table_name: String name of the source table.
+        :param elements: exclude conditions.
+        :param where: SQL expression or SQL string with optional WHERE
+         clause.
+        :param deferrable: optional bool. If set, emit DEFERRABLE or
+         NOT DEFERRABLE when issuing DDL for this constraint.
+        :param initially: optional string. If set, emit INITIALLY <value>
+         when issuing DDL for this constraint.
+        :param schema: Optional schema name to operate within.
+
+        """
+        op = cls(constraint_name, table_name, elements, **kw)
+        return operations.invoke(op)
+
+    @classmethod
+    def batch_create_exclude_constraint(
+        cls,
+        operations: BatchOperations,
+        constraint_name: str,
+        *elements: Any,
+        **kw: Any,
+    ) -> Optional[Table]:
+        """Issue a "create exclude constraint" instruction using the
+        current batch migration context.
+
+        .. note::  This method is Postgresql specific, and additionally
+           requires at least SQLAlchemy 1.0.
+
+        .. seealso::
+
+            :meth:`.Operations.create_exclude_constraint`
+
+        """
+        kw["schema"] = operations.impl.schema
+        op = cls(constraint_name, operations.impl.table_name, elements, **kw)
+        return operations.invoke(op)
+
+
+@render.renderers.dispatch_for(CreateExcludeConstraintOp)
+def _add_exclude_constraint(
+    autogen_context: AutogenContext, op: CreateExcludeConstraintOp
+) -> str:
+    return _exclude_constraint(op.to_constraint(), autogen_context, alter=True)
+
+
+@render._constraint_renderers.dispatch_for(ExcludeConstraint)
+def _render_inline_exclude_constraint(
+    constraint: ExcludeConstraint,
+    autogen_context: AutogenContext,
+    namespace_metadata: MetaData,
+) -> str:
+    rendered = render._user_defined_render(
+        "exclude", constraint, autogen_context
+    )
+    if rendered is not False:
+        return rendered
+
+    return _exclude_constraint(constraint, autogen_context, False)
+
+
+def _postgresql_autogenerate_prefix(autogen_context: AutogenContext) -> str:
+    imports = autogen_context.imports
+    if imports is not None:
+        imports.add("from sqlalchemy.dialects import postgresql")
+    return "postgresql."
+
+
+def _exclude_constraint(
+    constraint: ExcludeConstraint,
+    autogen_context: AutogenContext,
+    alter: bool,
+) -> str:
+    opts: List[Tuple[str, Union[quoted_name, str, _f_name, None]]] = []
+
+    has_batch = autogen_context._has_batch
+
+    if constraint.deferrable:
+        opts.append(("deferrable", str(constraint.deferrable)))
+    if constraint.initially:
+        opts.append(("initially", str(constraint.initially)))
+    if constraint.using:
+        opts.append(("using", str(constraint.using)))
+    if not has_batch and alter and constraint.table.schema:
+        opts.append(("schema", render._ident(constraint.table.schema)))
+    if not alter and constraint.name:
+        opts.append(
+            ("name", render._render_gen_name(autogen_context, constraint.name))
+        )
+
+    def do_expr_where_opts():
+        args = [
+            "(%s, %r)"
+            % (
+                _render_potential_column(
+                    sqltext,  # type:ignore[arg-type]
+                    autogen_context,
+                ),
+                opstring,
+            )
+            for sqltext, name, opstring in constraint._render_exprs
+        ]
+        if constraint.where is not None:
+            args.append(
+                "where=%s"
+                % render._render_potential_expr(
+                    constraint.where, autogen_context
+                )
+            )
+        args.extend(["%s=%r" % (k, v) for k, v in opts])
+        return args
+
+    if alter:
+        args = [
+            repr(render._render_gen_name(autogen_context, constraint.name))
+        ]
+        if not has_batch:
+            args += [repr(render._ident(constraint.table.name))]
+        args.extend(do_expr_where_opts())
+        return "%(prefix)screate_exclude_constraint(%(args)s)" % {
+            "prefix": render._alembic_autogenerate_prefix(autogen_context),
+            "args": ", ".join(args),
+        }
+    else:
+        args = do_expr_where_opts()
+        return "%(prefix)sExcludeConstraint(%(args)s)" % {
+            "prefix": _postgresql_autogenerate_prefix(autogen_context),
+            "args": ", ".join(args),
+        }
+
+
+def _render_potential_column(
+    value: Union[
+        ColumnClause[Any], Column[Any], TextClause, FunctionElement[Any]
+    ],
+    autogen_context: AutogenContext,
+) -> str:
+    if isinstance(value, ColumnClause):
+        if value.is_literal:
+            # like literal_column("int8range(from, to)") in ExcludeConstraint
+            template = "%(prefix)sliteral_column(%(name)r)"
+        else:
+            template = "%(prefix)scolumn(%(name)r)"
+
+        return template % {
+            "prefix": render._sqlalchemy_autogenerate_prefix(autogen_context),
+            "name": value.name,
+        }
+    else:
+        return render._render_potential_expr(
+            value,
+            autogen_context,
+            wrap_in_element=isinstance(value, (TextClause, FunctionElement)),
+        )
diff --git a/.venv/lib/python3.12/site-packages/alembic/ddl/sqlite.py b/.venv/lib/python3.12/site-packages/alembic/ddl/sqlite.py
new file mode 100644
index 00000000..7c6fb20c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/alembic/ddl/sqlite.py
@@ -0,0 +1,237 @@
+# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
+# mypy: no-warn-return-any, allow-any-generics
+
+from __future__ import annotations
+
+import re
+from typing import Any
+from typing import Dict
+from typing import Optional
+from typing import TYPE_CHECKING
+from typing import Union
+
+from sqlalchemy import cast
+from sqlalchemy import Computed
+from sqlalchemy import JSON
+from sqlalchemy import schema
+from sqlalchemy import sql
+
+from .base import alter_table
+from .base import ColumnName
+from .base import format_column_name
+from .base import format_table_name
+from .base import RenameTable
+from .impl import DefaultImpl
+from .. import util
+from ..util.sqla_compat import compiles
+
+if TYPE_CHECKING:
+    from sqlalchemy.engine.reflection import Inspector
+    from sqlalchemy.sql.compiler import DDLCompiler
+    from sqlalchemy.sql.elements import Cast
+    from sqlalchemy.sql.elements import ClauseElement
+    from sqlalchemy.sql.schema import Column
+    from sqlalchemy.sql.schema import Constraint
+    from sqlalchemy.sql.schema import Table
+    from sqlalchemy.sql.type_api import TypeEngine
+
+    from ..operations.batch import BatchOperationsImpl
+
+
+class SQLiteImpl(DefaultImpl):
+    __dialect__ = "sqlite"
+
+    transactional_ddl = False
+    """SQLite supports transactional DDL, but pysqlite does not:
+    see: http://bugs.python.org/issue10740
+    """
+
+    def requires_recreate_in_batch(
+        self, batch_op: BatchOperationsImpl
+    ) -> bool:
+        """Return True if the given :class:`.BatchOperationsImpl`
+        would need the table to be recreated and copied in order to
+        proceed.
+
+        Normally, only returns True on SQLite when operations other
+        than add_column are present.
+
+        """
+        for op in batch_op.batch:
+            if op[0] == "add_column":
+                col = op[1][1]
+                if isinstance(
+                    col.server_default, schema.DefaultClause
+                ) and isinstance(col.server_default.arg, sql.ClauseElement):
+                    return True
+                elif (
+                    isinstance(col.server_default, Computed)
+                    and col.server_default.persisted
+                ):
+                    return True
+            elif op[0] not in ("create_index", "drop_index"):
+                return True
+        else:
+            return False
+
+    def add_constraint(self, const: Constraint):
+        # attempt to distinguish between an
+        # auto-gen constraint and an explicit one
+        if const._create_rule is None:
+            raise NotImplementedError(
+                "No support for ALTER of constraints in SQLite dialect. "
+                "Please refer to the batch mode feature which allows for "
+                "SQLite migrations using a copy-and-move strategy."
+            )
+        elif const._create_rule(self):
+            util.warn(
+                "Skipping unsupported ALTER for "
+                "creation of implicit constraint. "
+                "Please refer to the batch mode feature which allows for "
+                "SQLite migrations using a copy-and-move strategy."
+            )
+
+    def drop_constraint(self, const: Constraint):
+        if const._create_rule is None:
+            raise NotImplementedError(
+                "No support for ALTER of constraints in SQLite dialect. "
+                "Please refer to the batch mode feature which allows for "
+                "SQLite migrations using a copy-and-move strategy."
+            )
+
+    def compare_server_default(
+        self,
+        inspector_column: Column[Any],
+        metadata_column: Column[Any],
+        rendered_metadata_default: Optional[str],
+        rendered_inspector_default: Optional[str],
+    ) -> bool:
+        if rendered_metadata_default is not None:
+            rendered_metadata_default = re.sub(
+                r"^\((.+)\)$", r"\1", rendered_metadata_default
+            )
+
+            rendered_metadata_default = re.sub(
+                r"^\"?'(.+)'\"?$", r"\1", rendered_metadata_default
+            )
+
+        if rendered_inspector_default is not None:
+            rendered_inspector_default = re.sub(
+                r"^\((.+)\)$", r"\1", rendered_inspector_default
+            )
+
+            rendered_inspector_default = re.sub(
+                r"^\"?'(.+)'\"?$", r"\1", rendered_inspector_default
+            )
+
+        return rendered_inspector_default != rendered_metadata_default
+
+    def _guess_if_default_is_unparenthesized_sql_expr(
+        self, expr: Optional[str]
+    ) -> bool:
+        """Determine if a server default is a SQL expression or a constant.
+
+        There are too many assertions that expect server defaults to round-trip
+        identically without parenthesis added so we will add parens only in
+        very specific cases.
+
+        """
+        if not expr:
+            return False
+        elif re.match(r"^[0-9\.]$", expr):
+            return False
+        elif re.match(r"^'.+'$", expr):
+            return False
+        elif re.match(r"^\(.+\)$", expr):
+            return False
+        else:
+            return True
+
+    def autogen_column_reflect(
+        self,
+        inspector: Inspector,
+        table: Table,
+        column_info: Dict[str, Any],
+    ) -> None:
+        # SQLite expression defaults require parenthesis when sent
+        # as DDL
+        if self._guess_if_default_is_unparenthesized_sql_expr(
+            column_info.get("default", None)
+        ):
+            column_info["default"] = "(%s)" % (column_info["default"],)
+
+    def render_ddl_sql_expr(
+        self, expr: ClauseElement, is_server_default: bool = False, **kw
+    ) -> str:
+        # SQLite expression defaults require parenthesis when sent
+        # as DDL
+        str_expr = super().render_ddl_sql_expr(
+            expr, is_server_default=is_server_default, **kw
+        )
+
+        if (
+            is_server_default
+            and self._guess_if_default_is_unparenthesized_sql_expr(str_expr)
+        ):
+            str_expr = "(%s)" % (str_expr,)
+        return str_expr
+
+    def cast_for_batch_migrate(
+        self,
+        existing: Column[Any],
+        existing_transfer: Dict[str, Union[TypeEngine, Cast]],
+        new_type: TypeEngine,
+    ) -> None:
+        if (
+            existing.type._type_affinity is not new_type._type_affinity
+            and not isinstance(new_type, JSON)
+        ):
+            existing_transfer["expr"] = cast(
+                existing_transfer["expr"], new_type
+            )
+
+    def correct_for_autogen_constraints(
+        self,
+        conn_unique_constraints,
+        conn_indexes,
+        metadata_unique_constraints,
+        metadata_indexes,
+    ):
+        self._skip_functional_indexes(metadata_indexes, conn_indexes)
+
+
+@compiles(RenameTable, "sqlite")
+def visit_rename_table(
+    element: RenameTable, compiler: DDLCompiler, **kw
+) -> str:
+    return "%s RENAME TO %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_table_name(compiler, element.new_table_name, None),
+    )
+
+
+@compiles(ColumnName, "sqlite")
+def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str:
+    return "%s RENAME COLUMN %s TO %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_column_name(compiler, element.column_name),
+        format_column_name(compiler, element.newname),
+    )
+
+
+# @compiles(AddColumn, 'sqlite')
+# def visit_add_column(element, compiler, **kw):
+#    return "%s %s" % (
+#        alter_table(compiler, element.table_name, element.schema),
+#        add_column(compiler, element.column, **kw)
+#    )
+
+
+# def add_column(compiler, column, **kw):
+#    text = "ADD COLUMN %s" % compiler.get_column_specification(column, **kw)
+# need to modify SQLAlchemy so that the CHECK associated with a Boolean
+# or Enum gets placed as part of the column constraints, not the Table
+# see ticket 98
+#    for const in column.constraints:
+#        text += compiler.process(AddConstraint(const))
+#    return text